最近沉迷于看《棋魂》,突发奇想做个棋子分拣机器。刚好涉及到的材料与之前写的XR806例程能差不多对应上,就差一个外壳,于是圣诞节就宅在实验室画了草图,趁着月黑风高偷偷用学校的CNC切了一个模型,基于XR806开发板和openHarmony实现了分拣棋子的功能。
先放个效果视频:
时间仓促,做工不是很精细,功能上也有待继续完善,比如说显示计数界面和联网功能云云····后面会慢慢填坑

最近沉迷于看《棋魂》,突发奇想做个棋子分拣机器。刚好涉及到的材料与之前写的XR806例程能差不多对应上,就差一个外壳,于是圣诞节就宅在实验室画了草图,趁着月黑风高偷偷用学校的CNC切了一个模型,基于XR806开发板和openHarmony实现了分拣棋子的功能。
先放个效果视频:
从垃圾堆里翻出来个电机,今天就玩它好了,对于电机的理解已经全部还给了大学老师,所以打算先研究一番:
百度了一下,这个东西学名叫作四相五线步进电机,电机就是这个圆陀陀,板子是他的驱动器。板载芯片是ULN2003,TI出品。查了一下技术文档,就是一个集成的达令顿管放大器。
很早以前就了解过:步进电机是吃脉冲信号工作,转化信号成相应的角位移和线位移。输入一个脉冲信号,转子就转动一个角度或前进一步,其输出的角位移或线位移与输入的脉冲数成正比,转速与脉冲频率成正比。因此,步进电动机又称脉冲电动机。
步进电机相对于其它控制用途电机的最大区别是,它接收数字控制信号(电脉冲信号)并转化成与之相对应的角位移或直线位移。也就是说,它本身就可以把信号转化成运动。而且它可开环位置控制,输入一个脉冲信号就得到一个规定的位置增量,与传统的直流控制系统相比,其成本明显减低,几乎不必进行系统调整。所以他给我们带来了很大的方便。要是想要他执行起来也很简单,只要控制脉冲的数量、频率和电机绕组的相序,即可获得所需的转角、速度和方向。
从具体的硬件来看,我这里需要接六条线才能让这个电机跑起来,电源端口就不说了,IN1~IN4这四个通道口要怎么输入信号才是问题所在。
如上就是内部原理图,右侧四个开关其实就是我们的四个信号端口。右侧ABCD为定子,上面绕有线圈,为四相,与之相对应的对面四个定子上面也有线圈,相对应的两个定子之间线圈是相互连接形成一个绕组。
下面来分析一种运行模式,单四拍运行:假如说现在我闭合0对着的那个开关,则由高中学过的电能生磁原理,0对应的线圈就会产生磁力,同时他的下端,也就是对应的B线圈,也就一起导通了。接下来B断开,C导通产生磁力,1和C(这里指的是右上角那个端)就会因为两者间夹角最小而互相吸引靠近,吸引过去后C断开,D通电,继续把离他最近的2吸引过去,如此一来类推下去就形成了一个逆时针旋转的过程。
上述过程直到0被吸引到A相时,就是一个周期的完成,旋转角度为360/8=45度。
还有其他控制方法,如双拍工作、单双拍工作等等,可以根据需求调节精度速度等等···决定先放一边,适当做点容易让人有成就感的事情:用代码实现这个过程,把舵机跑起来。
了解了原理就用最简单的方法去实现好了。主要代码如下:
IoTGpioSetOutputVal(12,0);
IoTGpioSetOutputVal(13,0);
IoTGpioSetOutputVal(19,1);
IoTGpioSetOutputVal(20,1);
OS_MSleep(2);
IoTGpioSetOutputVal(12,1);
IoTGpioSetOutputVal(13,0);
IoTGpioSetOutputVal(19,0);
IoTGpioSetOutputVal(20,1);
OS_MSleep(2);
IoTGpioSetOutputVal(12,1);
IoTGpioSetOutputVal(13,1);
IoTGpioSetOutputVal(19,0);
IoTGpioSetOutputVal(20,0);
OS_MSleep(2);
IoTGpioSetOutputVal(12,0);
IoTGpioSetOutputVal(13,1);
IoTGpioSetOutputVal(19,1);
IoTGpioSetOutputVal(20,0);
OS_MSleep(2);
像前面几篇一样,烧录程序,将对应IO连接上驱动板后,通上电,电机就正常工作了。
当然光转起来有些过于简单了,所以打算加一些可控元素进去,也方便后面做各种开发。于是又从垃圾堆里翻到了这个:
用翻出来的示波器研究了一下(没有万用表555),可以理解为由上下、左右两个方向的滑动变阻器和一个按键组成,总共三个信号输出口。保持在中间位置的时候测得是2.5v,移动向两边就是0跟5v,按下就是按钮。
既然是输出模拟信号最合理就是用ADC检测,无奈鸿蒙1.x.x的版本貌似都还没适配好806的接口,hal库的代码也找不到说明文档,用hal库尝试配置了一下ADC一直没成功,只能放到后面解决了。。。
所以最终决定还是用GPIO接口去读电平高低,具体代码如下:
#Create by Randolph.
import("//device/xradio/xr806/liteos_m/config.gni")
static_library("app_Stepper")
{
configs = []
sources = ["main.c",]
cflags = board_cflags
include_dirs = board_include_dirs
include_dirs += [
"//kernel/liteos_m/kernel/arch/include",
"//base/iot_hardware/peripheral/interfaces/kits",
"//device/xradio/xr806/xr_skylark/src/rom/rom_bin/src/driver",
]
}
main.c
//Create by Randolph
#include <stdio.h>
#include "kernel/os/os.h"
#include "ohos_init.h"
#include "iot_gpio.h"
static OS_Thread_t g_main_thread;
unsigned char mode;
static void gpio_init(void)
{
IoTGpioInit(11);
IoTGpioInit(12);
IoTGpioInit(13);
IoTGpioInit(19);
IoTGpioInit(20);
IotGpioDir dir;
IoTGpioSetDir(11, IOT_GPIO_DIR_IN);
IoTGpioGetDir(11, &dir);
IoTGpioSetDir(12, IOT_GPIO_DIR_OUT);
IoTGpioSetDir(13, IOT_GPIO_DIR_OUT);
IoTGpioSetDir(19, IOT_GPIO_DIR_OUT);
IoTGpioSetDir(20, IOT_GPIO_DIR_OUT);
}
static void Servo_mode(unsigned char mode)
{
if (mode == 1)
{
IoTGpioSetOutputVal(12,0);
IoTGpioSetOutputVal(13,0);
IoTGpioSetOutputVal(19,1);
IoTGpioSetOutputVal(20,1);
OS_MSleep(2);
IoTGpioSetOutputVal(12,1);
IoTGpioSetOutputVal(13,0);
IoTGpioSetOutputVal(19,0);
IoTGpioSetOutputVal(20,1);
OS_MSleep(2);
IoTGpioSetOutputVal(12,1);
IoTGpioSetOutputVal(13,1);
IoTGpioSetOutputVal(19,0);
IoTGpioSetOutputVal(20,0);
OS_MSleep(2);
IoTGpioSetOutputVal(12,0);
IoTGpioSetOutputVal(13,1);
IoTGpioSetOutputVal(19,1);
IoTGpioSetOutputVal(20,0);
OS_MSleep(2);
}
else if (mode == 0)
{
IoTGpioSetOutputVal(12,0);
IoTGpioSetOutputVal(13,1);
IoTGpioSetOutputVal(19,1);
IoTGpioSetOutputVal(20,0);
OS_MSleep(2);
IoTGpioSetOutputVal(12,1);
IoTGpioSetOutputVal(13,1);
IoTGpioSetOutputVal(19,0);
IoTGpioSetOutputVal(20,0);
OS_MSleep(2);
IoTGpioSetOutputVal(12,1);
IoTGpioSetOutputVal(13,0);
IoTGpioSetOutputVal(19,0);
IoTGpioSetOutputVal(20,1);
OS_MSleep(2);
IoTGpioSetOutputVal(12,0);
IoTGpioSetOutputVal(13,0);
IoTGpioSetOutputVal(19,1);
IoTGpioSetOutputVal(20,1);
OS_MSleep(2);
}
}
static void MainThread(void *arg)
{
while (1) {
IoTGpioGetInputVal(11,&mode);
Servo_mode(mode);
}
}
void Stepper_TestMain(void)
{
printf("Test Start\n");
gpio_init();
if (OS_ThreadCreate(&g_main_thread,"MainThread",MainThread, NULL, OS_THREAD_PRIO_APP, 4096) != OS_OK)
{
printf("[ERR] Create HelloWorld_Task Failed\n");
}
}
SYS_RUN(Stepper_TestMain);
看看效果:
至于为什么要顶个风扇叶呢,一方面是方便观察转动,另一方面也算是致敬一张大部分程序员应该都见过的图:
ok,又水了一贴,今天也是平安夜,祝各位圣诞快乐!
也挖个坑:下周会整合一下前面的内容做个小作品放出来。
Merry Christmas!
前言:之前有做过一个VLSI布局布线相关的课题,学了一点关于模拟退火算法的毛皮,今天来跑个demo复(shui)习(tie)一下 。
Simulated annealing这种算法的出发点是基于物理中固体物质的退火过程与一般组合优化问题之间的相似性。模拟退火算法是一种通用的优化算法,其物理退火过程由以下三部分组成:
(1)加温过程
(2)等温过程
(3)冷却过程
其中加温过程对应算法设定的初始温度,等温过程对应算法的Metropolis(一种采样算法)抽样过程,冷却过程对应控制参数的下降。这里能量的变化就是目标函数,要得到的最优解就是能量最低状态。Metropolis准则是SA算法收敛于全局最优解的关键所在,Metropolis准则以一定的概率接受恶化解。
总的来说就一句话:相比于类似爬山算法这种贪心算法,SA可以跳出局部最优解。
理解的大概原理后,就来看看代码。
/*
* 使用模拟退火算法(SA)求解TSP问题(以中国TSP问题为例)
* 参考自《Matlab 智能算法30个案例分析》
* 模拟退火的原理这里略去,可以参考上书或者相关论文
* update: 16/12/11
* author:lyrichu
* email:919987476@qq.com
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>
#include<math.h>
#define T0 50000.0 // 初始温度
#define T_end (1e-8)
#define q 0.98 // 退火系数
#define L 1000 // 每个温度时的迭代次数,即链长
#define N 31 // 城市数量
int city_list[N]; // 用于存放一个解
double city_pos[N][2] = {{1304,2312},{3639,1315},{4177,2244},{3712,1399},{3488,1535},{3326,1556},{3238,1229},{4196,1004},{4312,790},
{4386,570},{3007,1970},{2562,1756},{2788,1491},{2381,1676},{1332,695},{3715,1678},{3918,2179},{4061,2370},{3780,2212},{3676,2578},{4029,2838},
{4263,2931},{3429,1908},{3507,2367},{3394,2643},{3439,3201},{2935,3240},{3140,3550},{2545,2357},{2778,2826},{2370,2975}}; // 中国31个城市坐标
//函数声明
double distance(double *,double *); // 计算两个城市距离
double path_len(int *); // 计算路径长度
void init(); //初始化函数
void create_new(); // 产生新解
// 距离函数
double distance(double * city1,double * city2)
{
double x1 = *city1;
double y1 = *(city1+1);
double x2 = *(city2);
double y2 = *(city2+1);
double dis = sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
return dis;
}
// 计算路径长度
double path_len(int * arr)
{
double path = 0; // 初始化路径长度
int index = *arr; // 定位到第一个数字(城市序号)
for(int i=0;i<N-1;i++)
{
int index1 = *(arr+i);
int index2 = *(arr+i+1);
double dis = distance(city_pos[index1-1],city_pos[index2-1]);
path += dis;
}
int last_index = *(arr+N-1); // 最后一个城市序号
int first_index = *arr; // 第一个城市序号
double last_dis = distance(city_pos[last_index-1],city_pos[first_index-1]);
path = path + last_dis;
return path; // 返回总的路径长度
}
// 初始化函数
void init()
{
for(int i=0;i<N;i++)
city_list[i] = i+1; // 初始化一个解
}
// 产生一个新解
// 此处采用随机交叉两个位置的方式产生新的解
void create_new()
{
double r1 = ((double)rand())/(RAND_MAX+1.0);
double r2 = ((double)rand())/(RAND_MAX+1.0);
int pos1 = (int)(N*r1); //第一个交叉点的位置
int pos2 = (int)(N*r2);
int temp = city_list[pos1];
city_list[pos1] = city_list[pos2];
city_list[pos2] = temp; // 交换两个点
}
// 主函数
int main(void)
{
srand((unsigned)time(NULL)); //初始化随机数种子
time_t start,finish;
start = clock(); // 程序运行开始计时
double T;
int count = 0; // 记录降温次数
T = T0; //初始温度
init(); //初始化一个解
int city_list_copy[N]; // 用于保存原始解
double f1,f2,df; //f1为初始解目标函数值,f2为新解目标函数值,df为二者差值
double r; // 0-1之间的随机数,用来决定是否接受新解
while(T > T_end) // 当温度低于结束温度时,退火结束
{
for(int i=0;i<L;i++)
{
memcpy(city_list_copy,city_list,N*sizeof(int)); // 复制数组
create_new(); // 产生新解
f1 = path_len(city_list_copy);
f2 = path_len(city_list);
df = f2 - f1;
// 以下是Metropolis准则
if(df >= 0)
{
r = ((double)rand())/(RAND_MAX);
if(exp(-df/T) <= r) // 保留原来的解
{
memcpy(city_list,city_list_copy,N*sizeof(int));
}
}
}
T *= q; // 降温
count++;
}
finish = clock(); // 退火过程结束
double duration = ((double)(finish-start))/CLOCKS_PER_SEC; // 计算时间
printf("采用模拟退火算法,初始温度T0=%.2f,降温系数q=%.2f,每个温度迭代%d次,共降温%d次,得到的TSP最优路径为:\n",T0,q,L,count);
for(int i=0;i<N-1;i++) // 输出最优路径
{
printf("%d--->",city_list[i]);
}
printf("%d\n",city_list[N-1]);
double len = path_len(city_list); // 最优路径长度
printf("最优路径长度为:%lf\n",len);
printf("程序运行耗时:%lf秒.\n",duration);
return 0;
}
这是网上找到一个商旅问题的SA解决方法例子,稍加修改后交叉编译一下到开发板上执行。
保存为test.c文件到哪吒SDK下后,执行:
$/prebuilt/gcc/linux-x86/riscv/toolchain-thead-glibc/riscv64-glibc-gcc-thead_20200702/bin/riscv64-unknown-linux-gnu-gcc -o test -lm test.c
用adb传送生成的可执行文件到开发板上,执行:
$chmod +x test
$./test
即可,下面看看执行效果:
D1s麻雀:
root@TinaLinux:/# ./test
采用模拟退火算法,初始温度T0=50000.00,降温系数q=0.98,每个温度迭代1000次,共降温1448次,得到的TSP最优路径为:
22--->18--->3--->17--->19--->16--->4--->2--->8--->9--->10--->7--->6--->5--->23--->11--->13--->12--->14--->15--->1--->29--->31--->30--->27--->28--->26--->25--->24--->20--->21
最优路径长度为:15385.425778
程序运行耗时:17.578781秒.
root@TinaLinux:/# ./test
采用模拟退火算法,初始温度T0=50000.00,降温系数q=0.98,每个温度迭代1000次,共降温1448次,得到的TSP最优路径为:
5--->6--->7--->10--->9--->8--->2--->4--->16--->19--->17--->3--->18--->22--->21--->20--->24--->25--->26--->28--->27--->30--->31--->29--->1--->15--->14--->12--->13--->11--->23
最优路径长度为:15385.425778
程序运行耗时:17.594397秒.
root@TinaLinux:/# ./test
采用模拟退火算法,初始温度T0=50000.00,降温系数q=0.98,每个温度迭代1000次,共降温1448次,得到的TSP最优路径为:
14--->15--->1--->29--->31--->30--->27--->28--->26--->25--->23--->4--->8--->9--->10--->2--->7--->6--->5--->16--->19--->17--->3--->18--->22--->21--->20--->24--->11--->13--->12
最优路径长度为:16245.867392
程序运行耗时:17.584901秒.
D1-H哪吒:
root@TinaLinux:/# ./test
采用模拟退火算法,初始温度T0=50000.00,降温系数q=0.98,每个温度迭代1000次,共降温1448次,得到的TSP最优路径为:
4--->8--->9--->10--->2--->7--->6--->5--->23--->11--->13--->12--->14--->15--->1--->29--->31--->30--->27--->28--->26--->25--->24--->20--->21--->22--->18--->3--->17--->19--->16
最优路径长度为:15402.341890
程序运行耗时:17.511801秒.
root@TinaLinux:/# ./test
采用模拟退火算法,初始温度T0=50000.00,降温系数q=0.98,每个温度迭代1000次,共降温1448次,得到的TSP最优路径为:
15--->1--->29--->31--->30--->27--->28--->26--->22--->21--->20--->25--->24--->19--->17--->18--->3--->16--->4--->2--->8--->9--->10--->7--->6--->5--->23--->11--->13--->12--->14
最优路径长度为:15593.696331
程序运行耗时:17.513332秒.
root@TinaLinux:/# ./test
采用模拟退火算法,初始温度T0=50000.00,降温系数q=0.98,每个温度迭代1000次,共降温1448次,得到的TSP最优路径为:
12--->14--->15--->1--->31--->30--->25--->20--->21--->22--->18--->3--->17--->19--->24--->26--->28--->27--->29--->11--->23--->16--->4--->8--->9--->10--->2--->5--->6--->7--->13
最优路径长度为:16155.269857
程序运行耗时:17.514657秒.
可以看到时间都在17s左右,哪吒稍快一点点。不过可惜手里没有类似的开发板对比,只能拿一个猎奇一点的来对比:
H3夸克(稚晖君那个小电脑)
由于使用的是H3,就直接用GCC编译即可生成执行文件。
发现屏幕没有中字模哈哈,串口运行看看:
pi@Quark-N:~/WorkSpace/SA_Algorithm$ ./test
采用模拟退火算法,初始温度T0=50000.00,降温系数q=0.98,每个温度迭代1000次,共降温1448次,得到的TSP最优路径为:
27--->30--->31--->1--->15--->14--->13--->12--->29--->11--->23--->5--->6--->7--->10--->9--->8--->2--->4--->16--->19--->17--->3--->18--->22--->21--->20--->24--->25--->26--->28
最优路径长度为:15683.072504
程序运行耗时:39.690700秒.
pi@Quark-N:~/WorkSpace/SA_Algorithm$ ./test
采用模拟退火算法,初始温度T0=50000.00,降温系数q=0.98,每个温度迭代1000次,共降温1448次,得到的TSP最优路径为:
27--->31--->30--->25--->20--->24--->23--->5--->6--->11--->29--->1--->15--->14--->12--->13--->7--->10--->9--->8--->2--->4--->16--->19--->17--->3--->18--->22--->21--->26--->28
最优路径长度为:16072.436112
程序运行耗时:39.947321秒.
pi@Quark-N:~/WorkSpace/SA_Algorithm$ ./test
采用模拟退火算法,初始温度T0=50000.00,降温系数q=0.98,每个温度迭代1000次,共降温1448次,得到的TSP最优路径为:
19--->16--->4--->8--->9--->10--->2--->7--->6--->5--->23--->24--->20--->25--->29--->11--->13--->12--->14--->15--->1--->31--->30--->27--->28--->26--->21--->22--->18--->3--->17
最优路径长度为:15996.839897
程序运行耗时:39.952181秒.
将近40秒。而且不出所料,执行完后非常烫手(毕竟板子尺寸摆在那里)。前两者相比起来就较为风轻云淡。当然了这之间其实也没什么可比性,毕竟夸克还同时点着个屏幕,玩起来纯属娱乐哈哈,如果有大佬有兴趣也可以尝试优化一下,我还是希望起个抛砖引玉的作用 。
至于每次的结果为什么都不一样,简单按网上内容解释一下:SA算法本身是一种随机算法,转移向更差的解不是必然,而是概率性的,也就是说每次执行算法时,执行过程转移到的解可能都是完全不一样的。至于旅行商(Traveling Salesman Problem, TSP) 问题,本身属于NP-hard问题,找不到存在多项式时间复杂度的解。
如果要求精确的解,目前可行的方法有枚举、分支限界、动态规划等,但这些方法适用的数据范围都很小,一旦数据规模变大,它们都将无能为力。所以目前广泛使用的大都是一些随机算法,比如蚁群、遗传等,模拟退火就是其中的一种,这些算法的一大特点就是通过随机去逼近最优解,但也有可能得到错解。
最后贴上一个经典视频:
ok,今天的帖就水到这里。
分享一款二次元PCB设计,重新点燃了我的二次元之魂。
这个是我在翻CPU仿真相关的书籍时找到的一款配套开发板,这款CPU基板AZPR EvBoard(别名:基板少女) 由一个叫做" 从头开始做 CPU "的社团制作,搭载了XILINX公司的FPGA。可惜书是日文没有汉化。
JLC打了几片看看:
可惜如今已经买不起赛灵思的片子了
技术原理
关于技术实现,用到的材料主要有XR806开发板、两个9g的舵机,以及一个红外光传感器。
工作机制非常简单,如图所示:
上电后舵机复位到初始状态,任何开始工作,由一号舵机把待分类的棋子推移到检测区域,由红外传感器判断棋子的颜色,白色返回值为1,黑色则为0。
硬件模型制作
这一部分看似工作量大,但其实是参考了网上一些开源的例程,只不过加以了尺寸改动。
实现方法可以用3d打印或者激光切割亚克力,我选择了后者因为时间相对简短。使用的建模软件为Solidworks2019,亚克力板厚度后3mm,画好零件后,搭建好装配体确认尺寸无误后转为dxf格式即可用CNC机床切割。(我是用了学校的CNC,网上切也很便宜哈)
电路部分
使用的开发板为XR806,一款iot产品开发板,他最大的特点就是适配的轻鸿蒙,使用起来能有不错的体验。
需要驱动的设备就是两个舵机及一个红外传感器。
舵机就是一种位置伺服的驱动器,适用于那些需要角度不断变化并可以保持的控制系统。其工作原理是:控制信号由接收机的通道进入信号调制芯片,获得直流偏置电压。它内部有一个基准电路,产生周期为20ms,宽度为1.5ms的基准信号,将获得的直流偏置电压与电位器的电压比较,获得电压差输出。最后,电压差的正负输出到电机驱动芯片决定电机的正反转。当电机转速一定时,通过级联减速齿轮带动电位器旋转,使得电压差为0,电机停止转动。
以180度角度伺服为例,那么对应的控制关系是这样的:
0.5ms--------------0度;
1.0ms------------45度;
1.5ms------------90度;
2.0ms-----------135度;
2.5ms-----------180度;
拆开看看:
减速齿轮
驱动电路
因为片子上的字眼实在太小,令人“望眼欲穿”,本来还想查查有没有技术文档研究一下,只能放弃先了。刚好鸿蒙也适配了pwm的接口驱动xr806,所以我们只要修改freq频率和duty占空比两个选项如下即可。
IoTPwmStart(PWM_OUTPUT_CH0,8,50);
关于其他基础操作姿势可以参考我之前的一篇呼吸灯的demo帖子。还有一点值得一提的是关于这种舵机的输入信号口我是用806上的接口直接驱动的,并没有再拉高,内部是否需要或者有没有额外的驱动由于没有舵机内部电路的原理图也暂时无从得知,但总感觉在调节duty占空这一参数时舵机时而会不听使唤(占空比调制不准确),希望有大佬指点。
红外传感器原理则相对简单,一个红外发射管和一个红外接收管。模块通电后红外发射管向前方不断发射一定频率的红外线,
通过板载了一个电压比较器LM393
比较输出和输入的信号做出相应反馈,体现到我手里这块小板子上就是接收到红外信号为1,没有接收到(遇到黑色物体)则为0。所以读取判断值的代码相对简单:
IoTGpioGetInputVal(13,&mode);
整体代码部分
openHarmony用的是gn和ninja编译,由很多种上手学习方式,如果说gn对标的是gcc脚本构建生成器,ninja就对应的是makefile构建系统文档了。
#Create by Randolph.
import("//device/xradio/xr806/liteos_m/config.gni")
static_library("app_GO")
{
configs = []
sources = ["main.c",]
cflags = board_cflags
include_dirs = board_include_dirs
include_dirs += [
"//kernel/liteos_m/kernel/arch/include",
"//base/iot_hardware/peripheral/interfaces/kits",
"//device/xradio/xr806/xr_skylark/src/rom/rom_bin/src/driver",
]
}
main.c
//Create by Randolph
#include "kernel/os/os.h"
#include "ohos_init.h"
#include "iot_pwm.h"
#include "driver/chip/hal_pwm.h"
#define PWM_OUTPUT_CH0 PWM_GROUP0_CH0 //front
#define PWM_OUTPUT_CH1 PWM_GROUP0_CH1 //back
static OS_Thread_t g_main_thread;
unsigned char mode;
static void Servo_init(void)
{
IoTPwmInit(GPIO_PORT_A);
IoTGpioInit(13);
}
static void Servo_reset(void)
{
printf("0/n");
IoTPwmStart(PWM_OUTPUT_CH0,8,50);
OS_MSleep(500);
printf("1/n");
IoTPwmStart(PWM_OUTPUT_CH1,8,50);
OS_MSleep(500);
}
static void Servo_start(void)
{
printf("2/n");
IoTPwmStart(PWM_OUTPUT_CH1,5,50);
OS_MSleep(500);
IoTGpioGetInputVal(13,&mode);
printf("3/n");
IoTPwmStart(PWM_OUTPUT_CH1,8,50);
OS_MSleep(500);
if(mode == 1)
{
printf("4/n");
IoTPwmStart(PWM_OUTPUT_CH0,10,50);
OS_MSleep(500);
}
else
{
printf("4/n");
IoTPwmStart(PWM_OUTPUT_CH0,6,50);
OS_MSleep(500);
}
printf("5/n");
IoTPwmStart(PWM_OUTPUT_CH0,8,50);
OS_MSleep(500);
}
static void MainThread(void *arg)
{
Servo_init();
Servo_reset();
while (1)
{
Servo_start();
}
}
void PWM_TestMain(void)
{
printf("Test Start\n");
if (OS_ThreadCreate(&g_main_thread,"MainThread",MainThread, NULL, OS_THREAD_PRIO_APP, 4096) != OS_OK)
{
printf("[ERR] Create PWMLED_Task Failed\n");
}
}
SYS_RUN(PWM_TestMain);
最后在编译选项中加上即可
"GO_sorting:app_GO"
总结
到此基本功能是实现了,但有很多缺陷如我没有直观地体现出来分类后各种棋子的数量,也并没有给他加上控制开始和停止的操作。(没有一个对是否仍有棋子未被分类的判断机制)这些后续可以通过增加显示屏、替换红外传感器为压感贴片或者使用摄像头去学习判等等···最遗憾的是这个小demo暂时还没有发挥出XR806及鸿蒙的一些特征,如联网、连蓝牙。仅仅是个突发奇想的点子,希望能起到抛砖引玉的作用。后面我也会继续尝试给他加上其他功能。
再贴上一些前置学习内容:
官方文档:https://xr806.docs.aw-ol.com/
XR806开发板引脚功能表:https://bbs.aw-ol.com/topic/496/资料释放-xr806鸿蒙开发板引脚功能表
SDK获取到编译烧录:https://bbs.aw-ol.com/topic/499/xr806鸿蒙开发实战1-实操下载xr806鸿蒙代码并编译烧写
鸿蒙的外设接口:文件接口的头文件在:
base\iot_hardware\peripheral\interfaces\kits
源码在:
device\xradio\xr806\adapter\hals\iot_hardware\wifiiot_lite
带有iot字样的接口就是鸿蒙的接口。
端口复用:
Harmony\device\xradio\xr806\xr_skylark\project\common\board\xr806_OHOS\board_config.c
可以看到关于pwm的相关端口配置
__xip_rodata
static const GPIO_PinMuxParam g_pinmux_pwm[] = {
{ GPIO_PORT_A, GPIO_PIN_19, { GPIOA_P19_F4_PWM0_ECT0, GPIO_DRIVING_LEVEL_1, GPIO_PULL_NONE } },
{ GPIO_PORT_A, GPIO_PIN_20, { GPIOA_P20_F4_PWM1_ECT1, GPIO_DRIVING_LEVEL_1, GPIO_PULL_NONE } },
{ GPIO_PORT_A, GPIO_PIN_22, { GPIOA_P22_F4_PWM3_ECT3, GPIO_DRIVING_LEVEL_1, GPIO_PULL_NONE } },
{ GPIO_PORT_A, GPIO_PIN_12, { GPIOA_P12_F3_PWM4_ECT4, GPIO_DRIVING_LEVEL_1, GPIO_PULL_NONE } },
{ GPIO_PORT_A, GPIO_PIN_13, { GPIOA_P13_F3_PWM5_ECT5, GPIO_DRIVING_LEVEL_1, GPIO_PULL_NONE } },
{ GPIO_PORT_B, GPIO_PIN_3, { GPIOB_P3_F4_PWM7_ECT7, GPIO_DRIVING_LEVEL_1, GPIO_PULL_NONE } },
};
项目中所用到的引脚:
一号舵机信号线---------PA20
二号舵机信号线---------PA19
红外传感信号线---------PA13
用面包板引出开发板上的电源跟地供外设共用。
1、在device/config/chips/d1/configs下新建文件夹命名为d1-d1sevb,将同文件夹下的nezha的内容复制过去,并按照参考文章修改文件内容。
链接文本
2、package\allwinner\alsa-conf-aw\files\d1下新建文件夹d1-d1sevb,同样将同文件下nezha中的内容复制过去。
3、在target/allwinner下新建文件夹,命名为d1sevb,并将同文件夹下d1-nezha中的所有内容复制到d1sevb中,并修改文件名,及文件中的内容,使其指向d1sevb项目。
PRODUCT_NAME := d1_d1sevb
PRODUCT_DEVICE := d1-d1sevb
makefile中修改:
BOARD:=d1-d1sevb
BOARDNAME:=d1-d1sevb
TinaProducts.mk中修改:
PRODUCT_MAKEFILES := \
$(LOCAL_DIR)/d1_d1sevb.mk
vendorsetup.sh中修改:
add_lunch_combo d1_d1sevb-tina
4、根目录执行
souce build/environment setup
lunch后可以看到有新增的编译target
选择d1sevb,编译生成镜像,pack打包后用PhoenixSuit烧录。
成功进入系统。
如题,烧写完f133的镜像后串口没有任何反应,有没有大佬能提供一个可以正常工作的固件,还有自己的固件不知道为什么选择了一些视频功能插件就没办法正常编译。
本文以 PB-GATT 方式对 Mesh 的配网场景进行演示说明。通过演示移动设备给 Mesh 节点配网场景,简要介绍 mesh 命令的基本使用方法。
对于XR806原生SDK用户,Mesh 命令集成在 cmd_mesh.c 文件中,烧录相应demo,通过 ble_demo 工程可以完成 mesh 节点配网场景演示,ble_demo 工程位于 XR806 SDK 的/project/demo/bluetooth/ble_demo 目录。具体可参考文档XR806_Mesh控制台命令_使用指南.pdf 。
对于XR806鸿蒙SDK用户,执行以下操作:
cd device/xradio/xr806/xr_skylark
cp project/demo/wlan_ble_demo/gcc/deconfig .config
make menuconfig
进入图像化配置界面,打开ble mesh的相关选项。
可以看到图形化配置界面:
在BLE host中可见配置:
进入mesh配置可见:
配置后保存退出。
继续执行
make build_clen
make lib -j
cd -
hb build -f
可能遇到以下问题
问题1:可能会遇到空间不足报错的问题。
解决方案可以参考:
也可以取消
device\xradio\xr806\BUILD.gn
中其他内容的编译:
问题2:鸿蒙SDK没有BLE接口。
直接注释掉编译选项:
device/xradio/xr806/adapter/hals/BUILD.gn
编译完成后烧录镜像到开发板,通过串口打开命令行。
输入:
$ble init
$mesh init
可见:
== XRadio BLE HOST V2.5.0 ==
[cmd] Mesh initialized
[cmd] Use "pb-adv on" or "pb-gatt on" to enable advertising
<ACK> 200 OK
选用 PB-GATT 的承载方式:
$mesh pb-gatt on
可见:
[bt] [INF] bt_mesh_prov_enable: Device UUID: 00000000-0000-0000-0000-00000000dddd
[cmd] PB-GATT enabled
<ACK> 200 OK
$ *************************************************
[RandomAddress 57:F1:07:D8:B6:91 ]
同时需要手机提前下载安装nRF Mesh软件。
打开软件配网:
配网成功后,可以看到 nRF Mesh 网络列表看到该节点的信息,如下图。
点击红色方框,进入节点配置页面,可以看到节点的元素、模型等信息。
选择“Genenic On Off Server”模型进行操作:点击该模型选项,进入模型操作界面,如下图。进行模 型操作需要先进行“APP Key”绑定,点击“BIND KEY”,然后选择 App Key。
进行“Genenic On Off”:点击“ON”,就可以通过串口看到设备的 On/ Off 状态开了:
[cmd] onoff get(0)
[cmd] onoff set(1)
[cmd] onoff get(1)
[cmd] onoff get(1)
[cmd] onoff set(0)
[cmd] onoff set(0)
说明:Genenic On Off 操作会先去获取 OnOff 状态,然后再配置,配置完成后再获取一次状态。
完整log如下:
target thread = main
usage = 21, fragment = 0, maxFreeSize = 92740, totalFreeSize = 92740, waterLine = 26932
====================================================================
Hello! OpenHarmony!
System tag : OpenHarmony 1.1.2_LTS
====================================================================
target thread = pm
usage = 23, fragment = 0, maxFreeSize = 90660, totalFreeSize = 90660, waterLine = 29012
use default flash chip mJedec 0x0
[FD I]: mode: 0x10, freq: 96000000Hz, drv: 0
[FD I]: jedec: 0x0, suspend_support: 1
target thread = Looper
usage = 25, fragment = 1, maxFreeSize = 86836, totalFreeSize = 87636, waterLine = 32836
mode select:e
target thread = tcpip
usage = 27, fragment = 1, maxFreeSize = 84776, totalFreeSize = 85252, waterLine = 34420
target thread = umac
usage = 29, fragment = 1, maxFreeSize = 82716, totalFreeSize = 83020, waterLine = 36652
target thread = workqueue
usage = 31, fragment = 1, maxFreeSize = 80360, totalFreeSize = 80440, waterLine = 39320
target thread = rx_proc
usage = 34, fragment = 1, maxFreeSize = 77524, totalFreeSize = 77604, waterLine = 42068
target thread = BH
usage = 36, fragment = 1, maxFreeSize = 75464, totalFreeSize = 75544, waterLine = 44128
wlan information ===================================================
firmware:
version : R0-XR_C07.08.52.65_02.84 May 27 2021 11:41:33-Y02.84
buffer : 8
driver:
version : XR_V02.05
mac address:
in use : 0c:6e:88:3e:12:01
in use : 0c:6e:88:3e:12:02
====================================================================
target thread = wpas
usage = 40, fragment = 1, maxFreeSize = 69996, totalFreeSize = 70288, waterLine = 53648
wlan mode:a
[VFS INF] SPIFFS mount success.
platform information ===============================================
XR806 SDK v1.2.0 Jan 19 2022 10:44:04
heap space [0x22a884, 0x247c00), size 119676
cpu clock 160000000 Hz
HF clock 40000000 Hz
sdk option:
XIP : enable
INT LF OSC : enable
SIP flash : enable
mac address:
efuse : 80:74:84:05:ba:a0
in use : 0c:6e:88:3e:12:01
====================================================================
target thread = MainThread
usage = 49, fragment = 0, maxFreeSize = 60068, totalFreeSize = 60068, waterLine = 59604
target thread = hiview
usage = 52, fragment = 0, maxFreeSize = 55504, totalFreeSize = 55504, waterLine = 64168
console init success
hiview init success.target thread = console
usage = 54, fragment = 1, maxFreeSize = 53444, totalFreeSize = 53468, waterLine = 66228
$
$ ble init
target thread = LogMemTask
usage = 51, fragment = 11, maxFreeSize = 51384, totalFreeSize = 57576, waterLine = 66228
ble controller open
version : 9.1.19
build sha1 : v9.1.19-20210601
build date : Jun 1 2021
build time : 19:32:17
platform : xr806
ble rf_init done!
target thread = LinkLayer
usage = 58, fragment = 4, maxFreeSize = 47752, totalFreeSize = 49292, waterLine = 70380
target thread = LinkLayerHwEvent
usage = 60, fragment = 4, maxFreeSize = 44668, totalFreeSize = 46208, waterLine = 73464
target thread = CoexEvent
usage = 61, fragment = 4, maxFreeSize = 43632, totalFreeSize = 45172, waterLine = 74500
BLE INIT ALL DONE!
BT Coex. Init. OK.
== XRadio BLE HOST V2.5.0 ==
target thread = Zephyr
usage = 69, fragment = 2, maxFreeSize = 35484, totalFreeSize = 36096, waterLine = 83576
target thread = Zephyr
usage = 72, fragment = 2, maxFreeSize = 31376, totalFreeSize = 31988, waterLine = 87700
target thread = Zephyr
usage = 74, fragment = 3, maxFreeSize = 29316, totalFreeSize = 29928, waterLine = 89744
target thread = Zephyr
usage = 81, fragment = 3, maxFreeSize = 20504, totalFreeSize = 21116, waterLine = 98556
[bt] [WRN] set_flow_control: Controller to host flow control not supported
[bt] [INF] bt_init: No ID address. App must call settings_load()
[cmd] Bluetooth initialized
[bt] [INF] bt_dev_show_info: Identity: C6:42:D9:8F:39:BB (random)
[bt] [INF] bt_dev_show_info: HCI: version 5.0 (0x09) revision 0x0113, manufacturer 0x063d
[bt] [INF] bt_dev_show_info: LMP: version 5.0 (0x09) subver 0x0113
[cmd] Settings Loaded
<ACK> 200 OK
$ mesh init
== XRadio BLE HOST V2.5.0 ==
target thread = Zephyr
usage = 84, fragment = 4, maxFreeSize = 16396, totalFreeSize = 17008, waterLine = 102680
[cmd] Mesh initialized
[cmd] Use "pb-adv on" or "pb-gatt on" to enable advertising
<ACK> 200 OK
$ mesh pb-gatt on
[bt] [INF] bt_mesh_prov_enable: Device UUID: 00000000-0000-0000-0000-00000000dddd
[cmd] PB-GATT enabled
<ACK> 200 OK
$ *************************************************
[RandomAddress 57:F1:07:D8:B6:91 ]
*************************************************
[cmd] Connected: 78:C4:1B:66:EC:45 (random)
[cmd] Remote LMP version 5.0 (0x09) subversion 0x0000 manufacturer 0x0046
[cmd] LE Features: 0x00000000000179fd
[cmd] LE conn param updated: int 0x0006 lat 0 to 500
[cmd] Provisioning link opened on PB-GATT
[cmd] LE conn param updated: int 0x0027 lat 0 to 500
[cmd] LE data len updated: TX (len: 251 time: 17040) RX (len: 69 time: 5392)
[bt] [INF] bt_mesh_provision: Primary Element: 0x0003:
[cmd] Provisioning link closed on PB-GATT
[cmd] Local node provisioned, net_idx 0x0000 address 0x0003
[cmd] Disconnected: 78:C4:1B:66:EC:45 (random) (reason 0x13)
[cmd] Connected: 78:C4:1B:66:EC:45 (random)
[cmd] Remote LMP version 5.0 (0x09) subversion 0x0000 manufacturer 0x0046
[cmd] LE Features: 0x00000000000179fd
[cmd] LE conn param updated: int 0x0006 lat 0 to 500
[cmd] LE conn param updated: int 0x0027 lat 0 to 500
[cmd] LE data len updated: TX (len: 251 time: 17040) RX (len: 69 time: 5392)
[bt] [WRN] trans_ack: No matching TX context for ack
[cmd] onoff get(0)
[cmd] onoff set(1)
[cmd] onoff get(1)
[cmd] onoff get(1)
[cmd] onoff set(0)
[cmd] onoff get(0)
[cmd] onoff get(0)
[cmd] onoff set(1)
[cmd] onoff get(1)
[cmd] onoff get(1)
[cmd] onoff set(0)
[cmd] onoff get(0)
更新一下水个贴。
第一版各种问题还是挺多的,比如sd卡座封装画反了,画原理图脑抽把一个ddr供电口的网络标签搞成了文本 ,layout的时候也没注意,导致ddr没供电,只能跳线。。。。。
不过最后还是把系统启动了,皆大欢喜。
放点图:
git clone ssh://YOURNAME@sdk.allwinnertech.com/git_repo/XR806/xr806_sdk/xr806_sdk.git -b xr806_sdk
YOURNAME是自己在客户平台的用户名,我这里的环境是官方给的Ubuntu14,之前编译D1的tinaLinux用的。
拉代码之前务必确认正确添加了本机公钥。
可以看看目录结构:
allwinner@allwinner-VirtualBox:~/xr806_sdk$ tree -L 1
.
├── bin
├── ChangeLog.md
├── chip.mk
├── config.mk
├── gcc.mk
├── include
├── Kconfig
├── lib
├── Makefile
├── project
├── README.md
├── src
└── tools
6 directories, 7 files
打开README.md查看:
# XR806 SDK
XR806 SDK supports XR806 series wireless MCUs.
## Configuration
- Edit "gcc.mk" to define GCC path to your own path, eg.
```
CC_DIR = ~/tools/gcc-arm-none-eabi-8-2019-q3-update/bin
```
## Building
- Building commands
```
$ # make sure in SDK top dir
$ make PRJ=${prj} defconfig or cp ${prj_defconfig} .config
# eg. make PRJ=demo/wlan_demo defconfig or copy project/demo/wlan_demo/gcc/defconfig .config
$ make menuconfig # check configuration, like board/XTAL/XIP/CacheSize/...
$ make lib # build libraries and copy them to "lib"
$ make lib_clean # remove files in "src" generated by `make lib`
$ make lib_install_clean # Remove libraries in "lib" generated by `make lib`
$ make # build the executable binary
$ make clean # remove files generated by `make`
$ make image # create the image file
$ make image_clean # remove files generated by `make image`
$ make objdump # generate the disassembled file
$ make build # same as `make lib && make && make image`
$ make build_clean # same as `make image_clean clean lib_clean lib_install_clean`
```
## Links
- SDK: https://github.com/XradioTech/xr806-sdk.git
- WiKi: https://github.com/XradioTech/xradiotech-wiki.git
- DOC: https://docs.xradiotech.com
根据提示需要先指定编译器的位置
下载一下相应的编译器并解压:
gcc-arm-none-eabi-8-2019-q3-update-linux.tar.bz2
tar -jxvf gcc-arm-none-eabi-8-2019-q3-update-linux.tar.bz2
然后修改gcc.mk中指向的目录即可,如下:
CC_DIR := ~/gcc-arm-none-eabi-8-2019-q3-update/bin
后面详细列出了make方式,注意要在SDK最顶层下执行。
首先需要执行一下:
make menuconfig
然后保存退出即可。
然后执行:
make PRJ=demo/hello_demo
可以看到结果:
text data bss dec hex filename
39752 1576 2248 43576 aa38 hello_demo.elf
make[2]: Leaving directory `/home/allwinner/xr806_sdk/project/demo/hello_demo/gcc'
make[1]: Leaving directory `/home/allwinner/xr806_sdk/project/demo/hello_demo/gcc'
/home/allwinner/xr806_sdk
进入上一步的demo目录下:
cd project/demo/hello_demo/gcc/
执行
make image
可以看到:
Flash Layout:
sec bin 0 boot_40M.bin : flash_offs: 0x00000000( 0K) data_size: 0x00002F28( 12K)
sec bin 1 app.bin : flash_offs: 0x00004000( 16K) data_size: 0x00004920( 19K)
sec bin 2 app_xip.bin : flash_offs: 0x00029800( 166K) data_size: 0x000058D0( 23K)
generate image: xr_system.img
cp -t ../../../../out/ ../image/"xr806"/*.bin ../image/"xr806"/xr_system.img *.map
回到SDK根目录下可以看到out文件夹,进入后可见输出文件:
allwinner@allwinner-VirtualBox:~/xr806_sdk/out$ ls
app.bin app_xip.bin boot_40M.bin hello_demo.map sys_sdd_40M.bin wlan_bl.bin wlan_fw.bin xr_system.img
在SDK的tools文件夹下可见有烧录工具:
allwinner@allwinner-VirtualBox:~/xr806_sdk/tools$ ls
Blink.apk fs_img_tools mkimage.exe phoenixMC.dll settings.ini SoundConfig.apk
config HcidumpXr.7z mkimage_mac64 phoenixMC.exe settings_mac.ini
efuse_tool map_parse_gcc_v3.py phoenixMC phoenixMC_mac64 signpack.sh
etfGuiTool.exe mkimage phoenixMC_cli.exe sdd_editor SmartConfig.apk
由于想偷懒,没在虚拟机下烧录,直接把img和工具都拖到Windows下:
完成烧录:
我是用的是putty,波特率115200
熟悉的helloworld: