前面一篇博文介绍了用Arduino IDE上如何用ESP8266和NRF24L01+模块作为MySensors系统的终端,Arduino IDE有它的特点和好处,比如可以安装第三方提供的各种库,或者第三方提供的各种开发板的板级支持包(BSP),可以通过某些工具,比如串口等等自动烧录固件,但是它也有很大的缺点,比如说它的IDE严重占用系统资源,因为它是用java开发的,运行一个软件实例已经导致系统很卡,如果开多个实例,同时看代码,那会导致系统死机;还有它似乎也不支持文件同步修改,也就是说如果我用source Insight等代码编辑器写的代码,不能同步过来,每次都需要重新打开这个IDE加载整个工程才行;还有一个最严重的缺点,那就是:它编译速度实在令人难以忍受。。。作为项目开发,如果有其他的编译器,请果断抛弃Arduino IDE!
本文将在MySensors 2.3.1 版本源代码基础之上,做些修改,直接用在STM32的开发板上,并用 Keil-MDK作为IDE进行编译。
Keil-MDK(Keil uVision5)版本号: MDK-ARM Plus Version 5.23
本人使用的STM32开发板是比较老旧的一个金牛板,芯片是STM32F107VC.
RF模块:NRF24L01+ 无线模块 功率加强版 2.4G 无线收发通信模块。
还有一个5V1路继电器模块 小体积 (高电平触发吸合)
STM32F107VC | NRF24L01+ 模块 |
---|---|
3V3 | VCC (NRF24L01+须3.3V供电) |
GND | GND |
PA3 | CE |
PA4 (SPI1_NSS) | CSN (CS) |
PA5 (SPI1_SCK) | SCK |
PA6 (SPI1_MISO) | MISO |
PA7 (SPI1_MOSI) | MOSI |
注意:本文不使用NRF24L01+的中断控制功能,故它的IRQ脚不接
STM32F107VC | 继电器 |
---|---|
3V3 | VCC (改继电器模块可以接受3v到5v或者更宽的范围) |
GND | GND |
PB0 | S (或IN)(信号控制端) |
注意:本人使用的是比较便宜的继电器模块,没有光耦,直接跟STM32芯片的IO连接有安全上的风险,建议使用带光耦的模块。
具体的移植比较麻烦,就不一点点写上来了。
挑几个重要的地方说吧。
具体来说,去掉了做网关部分的代码,去掉了OTA(固件在线升级)部分的代码,去掉了加密、签名,只留最基本的通讯协议。留下的这部分,我们可以学习和熟悉MySensors的整个框架。有兴趣的朋友可以自己移植加密等部分。
本来MySensors框架需要用到存储芯片,用来存取节点ID和父节点ID等信息。但是我们在这次移植中为了简便,就省略了这部分代码,但在应用程序部分直接给定节点ID和父节点ID(默认为MY_PARENT_NODE_ID,默认初始值是255,表示尚未连接到父节点).。只要在正常任务运行之前,指定这两个ID,就不影响整体的运行。
首先说明一点:本次移植基于开发板使用FreeRTOS操作系统。但是,整个MySensors框架本质上是独立于操作系统的,移植过程大部分跟操作系统无关。如无操作系统(No OS),只要实现hwMillis、hwSleep、delay(ms)、delayMicroseconds(ms)函数,并写好NRF24模块的底层驱动,能够顺利收发数据就可以正常使用MySensors框架。
移植的文件 | 位置 | 功能 |
---|---|---|
MyHwSTM32F1.cpp | MySensors/hal/architecture/STM32F1/ | 实现STM32芯片上的几个最基本的函数,例如hwMillis(获取系统运行毫秒节拍数)、hwSleep(任务睡眠函数)、hwDebugPrint(打印调试信息函数)。 |
RF24.cpp | MySensors/hal/transport/RF24/driver/ | 实现NRF24L01+ 无线模块和STM32芯片的底层驱动(板子使用的是25Mhz晶振,运行主频是72Mhz)。 |
RF24.cpp | MySensors/hal/transport/RF24/ | 这部分属于进一步封装无线模块收发数据接口的适配层(这部分主要是是因为MySensors系统不只是针对NRF24模块控制,而是面向更多的无线模块,利用这层封装来适配这些模块)。 |
MyMessage.cpp | MySensors/core/ | 定义节点消息数据包结构,以及对数据值的读取处理等。每个数据包最长32字节(Byte),最短7字节。其中包括最基本的7字节消息头。payload(有效载荷)部分长度是0~25字节,最长25字节。具体请参考(Serial Protocol - 2.x 协议):https://www.mysensors.org/download/serial_api_20 |
MyTransport.cpp | MySensors/core/ | 传输层的实现,MySensors框架核心部分之一,主要按照通信协议对消息进行解析,实现一个控制通信状态的状态机机制,并通过解析后的消息来控制状态机。它有六个状态:stInit(初始化,获取节点ID、父节点ID等信息)、 stParent(寻找父节点)、 stID(确认终端ID合法性或请求向MySensors网关请求注册一个ID)、stUplink(把本节点连接到MySensors网关进行级联)、stReady(连接到MySensors网关成功,为更进一步无线数据传输功能准备就绪)、stFailure(连接MySensors网关失败)。另外,实现了节点之间发送消息、节点向网关或路由(即MySensors中继器)发送消息的函数,以及控制无线模块(例如但不限于NRF24无线模块)发送消息数据的底层实现。 |
MySensorsCore.cpp | MySensors/core/ | MySensors框架核心部分之二,主要是管理MySensors系统的整体运行,并封装常用的API函数。 |
注意:下面的流程主要是对指定的动态ID进行注册的过程。
如果是指定静态ID,注册的过程则稍有不同。
【注意】:框架使用了MY_REGISTRATION_FEATURE (注册特性),也就是说该节点在发送传感器数据前,必须向网关或控制器进行注册。控制器是指另外一个节点,它对传感器节点进行响应或其他操作。
以终端节点ID=3为例(网关ID默认为0,负责广播的节点ID=255)(D表示Destination——目标节点):
这种情况实际是跟A情况过程一样的。
终端节点:
###############################################
## hello! welcome to FreeRTOS v9.0.0 ##
###############################################
main.c,line:177,
RF24Task.cpp,line:122,vTaskRF24L01()
0 MCO:BGN:INIT NODE,REL=255,VER=2.3.1
1 MCO:BGN:BFR
1 TSM:INIT
1 TSF:WUR:MS=0
2 RF24:INIT
2 RF24:SBY
1003 RF24:FRX
1003 RF24:FTX
1004 TSM:INIT:TSP OK
1005 RF24:STL
1005 TSF:SID:OK,ID=3
1006 TSM:FPAR
MyTransport.cpp,line:942,totalMsgLength=7
1007 RF24:SPL
1008 RF24:OWP:RCPT=255
1009 RF24:TXM:TO=255,LEN=7
1009 RF24:FTX
1015 !RF24:TXM:MAX_RT
1015 RF24:FTX
1016 RF24:STL
1016 TSF:MSG:SEND,3-3-255-255,s=255,c=3,t=7,pt=0,l=0,sg=0,ft=0,st=OK:
1143 RF24:RXM:LEN=8
1144 TSF:MSG:READ,0-0-3,s=255,c=3,t=8,pt=1,l=1,sg=0:0
1145 TSF:MSG:FPAR OK,ID=0,D=1
3019 TSM:FPAR:OK
3019 TSM:ID
3020 TSM:ID:OK
3020 TSM:UPL
MyTransport.cpp,line:942,totalMsgLength=8
3021 RF24:SPL
3022 RF24:OWP:RCPT=0
3022 RF24:TXM:TO=0,LEN=8
3023 RF24:FTX
3024 RF24:STL
3025 TSF:MSG:SEND,3-3-0-0,s=255,c=3,t=24,pt=1,l=1,sg=0,ft=0,st=OK:1
3027 RF24:RXM:LEN=8
3028 TSF:MSG:READ,0-0-3,s=255,c=3,t=25,pt=1,l=1,sg=0:1
3029 TSF:MSG:PONG RECV,HP=1
3030 TSM:UPL:OK
3031 TSM:READY:ID=3,PAR=0,DIS=1
MyTransport.cpp,line:942,totalMsgLength=9
3032 RF24:SPL
3033 RF24:OWP:RCPT=0
3034 RF24:TXM:TO=0,LEN=9
3034 RF24:FTX
3035 RF24:STL
3036 TSF:MSG:SEND,3-3-0-0,s=255,c=3,t=15,pt=6,l=2,sg=0,ft=0,st=OK:0100
3038 RF24:RXM:LEN=9
3039 TSF:MSG:READ,0-0-3,s=255,c=3,t=15,pt=6,l=2,sg=0:0100
MyTransport.cpp,line:942,totalMsgLength=12
3042 RF24:SPL
3042 RF24:OWP:RCPT=0
3043 RF24:TXM:TO=0,LEN=12
3044 RF24:FTX
3045 RF24:STL
3045 TSF:MSG:SEND,3-3-0-0,s=255,c=0,t=17,pt=0,l=5,sg=0,ft=0,st=OK:2.3.1
MyTransport.cpp,line:942,totalMsgLength=8
3048 RF24:SPL
3049 RF24:OWP:RCPT=0
3049 RF24:TXM:TO=0,LEN=8
3050 RF24:FTX
3051 RF24:STL
3052 TSF:MSG:SEND,3-3-0-0,s=255,c=3,t=6,pt=1,l=1,sg=0,ft=0,st=OK:0
3063 RF24:RXM:LEN=8
3064 TSF:MSG:READ,0-0-3,s=255,c=3,t=6,pt=0,l=1,sg=0:M
RF24Task.cpp,line:40,presentation()
MyTransport.cpp,line:942,totalMsgLength=16
3067 RF24:SPL
3067 RF24:OWP:RCPT=0
3068 RF24:TXM:TO=0,LEN=16
3069 RF24:FTX
3070 RF24:STL
3071 TSF:MSG:SEND,3-3-0-0,s=255,c=3,t=11,pt=0,l=9,sg=0,ft=0,st=OK:RF24 test
MyTransport.cpp,line:942,totalMsgLength=10
3074 RF24:SPL
3074 RF24:OWP:RCPT=0
3075 RF24:TXM:TO=0,LEN=10
3076 RF24:FTX
3077 RF24:STL
3077 TSF:MSG:SEND,3-3-0-0,s=255,c=3,t=12,pt=0,l=3,sg=0,ft=0,st=OK:1.0
MyTransport.cpp,line:942,totalMsgLength=7
3080 RF24:SPL
3081 RF24:OWP:RCPT=0
3081 RF24:TXM:TO=0,LEN=7
3082 RF24:FTX
3083 RF24:STL
3084 TSF:MSG:SEND,3-3-0-0,s=1,c=0,t=0,pt=0,l=0,sg=0,ft=0,st=OK:
MyTransport.cpp,line:942,totalMsgLength=11
3086 RF24:SPL
3086 RF24:OWP:RCPT=0
3087 RF24:TXM:TO=0,LEN=11
3088 RF24:FTX
3089 RF24:STL
3090 TSF:MSG:SEND,3-3-0-0,s=3,c=0,t=6,pt=0,l=4,sg=0,ft=0,st=OK:Temp
3091 MCO:REG:REQ
MyTransport.cpp,line:942,totalMsgLength=8
3092 RF24:SPL
3093 RF24:OWP:RCPT=0
3094 RF24:TXM:TO=0,LEN=8
3094 RF24:FTX
3095 RF24:STL
3096 TSF:MSG:SEND,3-3-0-0,s=255,c=3,t=26,pt=1,l=1,sg=0,ft=0,st=OK:2
3098 RF24:RXM:LEN=8
3099 TSF:MSG:READ,0-0-3,s=255,c=3,t=27,pt=1,l=1,sg=0:1
3101 MCO:PIM:NODE REG=1
3101 MCO:BGN:STP
MyTransport.cpp,line:942,totalMsgLength=7
3103 RF24:SPL
3103 RF24:OWP:RCPT=0
3104 RF24:TXM:TO=0,LEN=7
3105 RF24:FTX
3106 RF24:STL
3106 TSF:MSG:SEND,3-3-0-0,s=1,c=2,t=2,pt=0,l=0,sg=0,ft=0,st=OK:
3108 MCO:BGN:INIT OK,TSP=1
3136 RF24:RXM:LEN=8
3137 TSF:MSG:READ,0-0-3,s=1,c=2,t=2,pt=0,l=1,sg=0:0
status=0
网关:
1901540 TSF:MSG:READ,3-3-255,s=255,c=3,t=7,pt=0,l=0,sg=0:
1901545 TSF:MSG:BC
1901547 TSF:MSG:FPAR REQ,ID=3
1901550 TSF:PNG:SEND,TO=0
1901552 TSF:CKU:OK
1901554 TSF:MSG:GWL OK
1902570 TSF:MSG:SEND,0-0-3-3,s=255,c=3,t=8,pt=1,l=1,sg=0,ft=0,st=OK:0
1917238 TSF:MSG:READ,3-3-0,s=255,c=3,t=24,pt=1,l=1,sg=0:1
1917243 TSF:MSG:PINGED,ID=3,HP=1
1917253 TSF:MSG:SEND,0-0-3-3,s=255,c=3,t=25,pt=1,l=1,sg=0,ft=0,st=OK:1
1917326 TSF:MSG:READ,3-3-0,s=255,c=3,t=15,pt=6,l=2,sg=0:0100
1917335 TSF:MSG:SEND,0-0-3-3,s=255,c=3,t=15,pt=6,l=2,sg=0,ft=0,st=OK:0100
1917398 TSF:MSG:READ,3-3-0,s=255,c=0,t=17,pt=0,l=5,sg=0:2.3.1
1917403 GWT:TPS:TOPIC=domoticz/in/MyMQTT/3/255/0/0/17,MSG SENT
PresentationMessage
1917449 TSF:MSG:READ,3-3-0,s=255,c=3,t=6,pt=1,l=1,sg=0:0
1917454 GWT:TPS:TOPIC=domoticz/in/MyMQTT/3/255/3/0/6,MSG SENT
1917531 GWT:IMQ:TOPIC=domoticz/out/MyMQTT/3/255/3/0/6, MSG RECEIVED
1917539 TSF:MSG:SEND,0-0-3-3,s=255,c=3,t=6,pt=0,l=1,sg=0,ft=0,st=OK:M
1917597 TSF:MSG:READ,3-3-0,s=255,c=3,t=11,pt=0,l=9,sg=0:RF24 test
1917603 GWT:TPS:TOPIC=domoticz/in/MyMQTT/3/255/3/0/11,MSG SENT
1917648 TSF:MSG:READ,3-3-0,s=255,c=3,t=12,pt=0,l=3,sg=0:1.0
1917653 GWT:TPS:TOPIC=domoticz/in/MyMQTT/3/255/3/0/12,MSG SENT
1917694 TSF:MSG:READ,3-3-0,s=1,c=0,t=0,pt=0,l=0,sg=0:
1917699 GWT:TPS:TOPIC=domoticz/in/MyMQTT/3/1/0/0/0,MSG SENT
PresentationMessage
1917741 TSF:MSG:READ,3-3-0,s=3,c=0,t=6,pt=0,l=4,sg=0:Temp
1917746 GWT:TPS:TOPIC=domoticz/in/MyMQTT/3/3/0/0/6,MSG SENT
PresentationMessage
1917792 TSF:MSG:READ,3-3-0,s=255,c=3,t=26,pt=1,l=1,sg=0:2
1917804 TSF:MSG:SEND,0-0-3-3,s=255,c=3,t=27,pt=1,l=1,sg=0,ft=0,st=OK:1
1917873 TSF:MSG:READ,3-3-0,s=1,c=2,t=2,pt=0,l=0,sg=0:
1917878 GWT:TPS:TOPIC=domoticz/in/MyMQTT/3/1/2/0/2,MSG SENT
1918102 GWT:IMQ:TOPIC=domoticz/out/MyMQTT/3/1/2/0/2, MSG RECEIVED
1918110 TSF:MSG:SEND,0-0-3-3,s=1,c=2,t=2,pt=0,l=1,sg=0,ft=0,st=OK:0
1944523 GWT:IMQ:TOPIC=domoticz/out/MyMQTT/0/0/3/0/18, MSG RECEIVED
1944529 GWT:TPS:TOPIC=domoticz/in/MyMQTT/0/255/3/0/22,MSG SENT
其实移植的大部分源代码不需要太多改动,主要是对NRF24L01模块的驱动做一些调整。
只要严格按照它手册上的时序来调,就比较容易调出来。
如果读者有条件,强烈建议使用示波器来测量时序。
调试的难点在于,如果NRF24驱动中的延迟不当,会造成模块与主控芯片基本通信失败。
应该在保证基本通信正常的情况下进行传输层的通信调试。
基本通信是指主控芯片STM32对NRF24L01模块寄存器的读写,以及由发射状态转入接收状态后能否及时收到数据。
我们使用的是动态配置ID,所以在注册节点ID之前,需要在程序中初始化节点ID和父节点ID。
笔者已经添加了设置这两个ID的API函数:
void setCFG_ParentNodeId(const uint8_t id);
void setCFG_NodeId(const uint8_t id);
设置节点ID和父节点ID可以在应用程序中定义before函数,该函数体中实现:
void before()
{
//设置父节点ID,默认为255
setCFG_ParentNodeId(MY_PARENT_NODE_ID);
//设置本NRF24模块节点ID为3
setCFG_NodeId(3);
}
默认网关的ID为0,广播节点ID为255。
所有本无线网络内节点的父节点ID就默认设置为255。
节点ID可以视情况而定,可以是1~254中的任意值,但需要保证这个无线网络内的任意两个节点ID不要重复。
当节点发送消息时,最初是发给广播节点255,网关默认负责广播节点255的消息处理,连接成功后,网关发消息给终端节点。
之后终端节点会更改父节点ID为网关ID(默认为0)。
传输层的通信一个重要点在于注册节点ID有个wait函数调用,默认超时设置为2000ms(即2秒),所以,需要在这个超时时间内完成注册的确认,如果驱动程序在基本通信延迟很严重的话,那么就会导致注册不成功。
因为代码比较多,就不贴上来了。
提供一个下载地址:
移植MySensors终端框架代码到Keil-MDK
代码的编译使用到 Keil-MDK,需要加入 ARDUINO_ARCH_STM32F1 这个宏定义 和 --gnu,和设置如图:
加入“–gnu”,是因为编译MyMessage这个代码模块的时候,这个类的数据结构中包含几个结构体,并且,代码使用中,采用了匿名方式访问,就是说,这些结构体并没有起名字,兼容GNU标准的编译器是允许这样做的,但是其他的编译器很难说。
就像Keil-MDK,需要加上“–gnu”这个参数来支持匿名访问特性。
Relay1Driver.h :
/******************************************************************************
* ÎļþÃû: Relay1Driver.h
* ×¢:
*
* ¼òÊö: A brief file description.
* ÏêÊö:
* A more elaborated file description.
* ´´½¨Õß:
* ´´½¨Ê±¼ä:2011-Apr-20 23:57:33
* ÐÞ¸ÄÕß:
* ÐÞ¸Äʱ¼ä:2011-Apr-20 23:57:33
* °æ±¾¼Ç¼:
******************************************************************************/
#ifndef STEPMOTOR_DRIVER_H
#define STEPMOTOR_DRIVER_H
#ifdef __cplusplus
extern "C"{
#else
#include
#endif
void Relay1_Init();
void Relay1_SetOnOrOff(bool on);
#ifdef __cplusplus
}
#endif
#endif
/*-- File end --*/
Relay1Driver.c :
/******************************************************************************
* 文件名: Relay1Driver.c
* 注:
*
* 简述: A brief file description.
* 详述:
* A more elaborated file description.
* 创建者:
* 创建时间:2011-Apr-20 23:53:21
* 修改者:
* 修改时间:2011-Apr-20 23:53:21
* 版本记录:
******************************************************************************/
#include "Relay1Driver.h"
#include "stm32f10x_gpio.h"
#define _DEBUG
#include "dprintf.h"
/*-- #include --*/
#include "stm32f10x.h"
#define RELAY_1_PB_RCC RCC_APB2Periph_GPIOB
#define RELAY_1_PB_GPIO GPIOB
#define RELAY_1_PB_PIN (GPIO_Pin_0)
typedef struct
{
GPIO_TypeDef* GPIOx;
uint16_t GPIO_Pin;
}Relay;
void Relay1_Init();
void Relay1_SetOnOrOff(bool on);
Relay relay1={RELAY_1_PB_GPIO,RELAY_1_PB_PIN};
void Relay1_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RELAY_1_PB_RCC,ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = relay1.GPIO_Pin;
GPIO_Init(relay1.GPIOx, &GPIO_InitStructure);
}
void Relay1_SetOnOrOff(bool on)
{
if(on==true)
{
GPIO_SetBits(relay1.GPIOx, relay1.GPIO_Pin);
}
else
{
GPIO_ResetBits(relay1.GPIOx, relay1.GPIO_Pin);
}
}
/*-- File end --*/
main.c 代码片段:
#include "RF24Task.h"
#define NRF24_TASK_PRIO ( tskIDLE_PRIORITY )
static TaskHandle_t xHandleRF24Task=NULL;
int main(void)
{
__set_PRIMASK(1);//禁止全局中断
prvSetupHardware();
FreeRTOS_printf_service_init();
printf("###############################################\r\n");
printf("## hello! welcome to FreeRTOS v9.0.0 ##\r\n");
printf("###############################################\r\n");
printf("\r\n\r\n");
dprintf("\n");
xTaskCreate(vTaskLED,"vTaskLED",ledSTACK_SIZE,NULL,3,&xHandleTaskLED);
xTaskCreate(vTaskRF24L01,"vTaskRF24L01",256,NULL,NRF24_TASK_PRIO,&xHandleRF24Task);
vTaskStartScheduler();//启动任务调度器
}
#include "FreeRTOS.h"
#include "task.h"
#include "Relay1Driver.h"
#define _DEBUG
#include "dprintf.h"
#include "MySensors.h"
#include "MyMessage.h"
#define CHILD_ID_DOOR 1
#define CHILD_ID_TEMP 3
MyMessage msgTemp(CHILD_ID_TEMP,V_TEMP);
/**
* For initialisations that needs to take place before MySensors transport has been setup (eg: SPI devices).
*/
//before函数在MySensors框架代码中被调用,而且是在执行setup()函数之前被调用
void before()
{
//设置父节点ID,默认为255
setCFG_ParentNodeId(MY_PARENT_NODE_ID);
//设置本NRF24模块节点ID为3
setCFG_NodeId(3);
}
/**
* This allows controller to re-request presentation and do re-configuring node after startup.
*/
//presentation函数在MySensors框架代码中被调用,
//它允许控制器重新请求节点介绍信息,并在节点建立后重新进行配置
void presentation()
{dprintf("presentation()\n");
// Present locally attached sensors here
sendSketchInfo("RF24 test", "1.0",false);
present(CHILD_ID_DOOR, S_DOOR,"",false);
present(CHILD_ID_TEMP, S_TEMP,"Temp",0);//S_TEMP
}
/**
* Called once at startup, usually used to initialize sensors.
*/
void setup()
{
Relay1_Init();//继电器1硬件初始化
Relay1_SetOnOrOff(false);//断开继电器1
//向网关请求传感器id=CHILD_ID_DOOR的状态值(V_STATUS)
request(CHILD_ID_DOOR, V_STATUS,GATEWAY_ADDRESS);
//request(MS_RELAY2_CHILD_ID, V_STATUS);
//request(MS_LEDPWM_CHILD_ID, V_DIMMER);
}
void loop()
{//dprintf("loop()\n");
// Send locally attached sensor data here
}
//receive函数在MySensors框架代码中被调用
void receive(const MyMessage &message)
{
if( message.sensor == CHILD_ID_DOOR )
{
if( message.type == V_STATUS )
{
bool status = message.getBool();//从消息中获取状态值
if( status )
{
printf("status=1\n");
Relay1_SetOnOrOff(true);//吸合继电器1
}
else
{
printf("status=0\n");
Relay1_SetOnOrOff(false);//断开继电器1
}
}
}
}
//无线模块任务
extern "C" void vTaskRF24L01(void* pvParameters)
{
dprintf("vTaskRF24L01()\n");
_begin(); //Startup MySensors library
for(;;)
{
_process(); //处理接收到的数据
loop(); // 调用 sketch loop
}
}
实际效果如图: