在上一章节,笔者带领大家已经将机智云平台玩起来,本节内容讲带领大家经进一步开发。
在开始讲解之前,有必要先了解的机智云的平台架构。
从上面的架构图可以看到,对于一般的开发者而言,我们重点要关注的就是设备端,也就是开发的重点,要想将各个传感器和模组与云端进行数据交互,那就必须在设备端实现数据的采集以及设备的控制,这些实现就需要开发者去完成。
因此本文将代码大家基于STM32实现相应的驱动开发。
2.1机智云产品创建与开发这部分内容就不在赘述了,和上一章节内容一样,只是笔者这里的新建了很多数据点,当然这里需要根据自己的实际情况来配置,GoKit3的扩展板有丰富的资源,红外探测器,可编程全彩 RGB灯,可编程电机,温湿度传感器,三个可编程按键等,笔者这里选取RGB灯、温湿度、电机为例进行讲解。
因此产品信息对应配置如下:
然后关联相关的应用。
然后构建相应的应用即可。
云端和客户端比较简单。
2.2设备驱动开发2.2.1硬件方案生成与配置在设备开发之前,还需要在云端生成相应的应用方案。
将生成的硬件方案下载下来后,即可进行相应接口的初始化。
相应的硬件接口如下所示:
硬件 | 接口 |
RGB LED | PB8, PB9 |
温湿度传感器 | PB3 |
可编程电机 | PB4, PB5 |
打开STM32F103C8x.ioc,将PA0, PB8, PB9,PB3分别配置成输出模式。
可编程电机使用PWM控制,因此是打开相应的PWM通道。
然后生成相应的初始化代码。
接下来就针对上述硬件进行开发。
2.2.2 RGB驱动开发GoKit3的扩展板上设计了一个 RGB灯,通过 P9813 驱动,P9813芯片采用 CMOS 工艺,提供三路恒流驱动及 256 级灰度调制输出,可显示多达256*256*256 种颜色,性能优良,可视效果分明。其驱动
威廉希尔官方网站
如下。
其中CIN和DIN分别是时钟线和数据线。
P9813模块通讯采用双线传输方式(DATA、 CLK),其
通信方式与 I2C 通信类似。 P9813芯片具体通信协议及时序如图所示。
① 前32 位“0”为起始帧,在Cin 上升沿时打入,并且时序DIN要先于CIN;
② 标志位为两个“1”;
③ 校验数据“B7’”与“B6’”为蓝色灰度数据的“B7”与“B6”的反码;
④ 灰度级数据要高位先入,并且是蓝绿红顺序。
从图可以看到通讯协议还是比较简单。
32位0起始信号: 0000 0000 00000000 0000 0000 0000 0000
第一点32位灰度数据: 11B7’B6’ G7’G6’R7’R6’ xxxx xxxx xxxxxxxx xxxx xxxx
第二点32位灰度数据: 11B7’B6’ G7’G6’R7’R6’ xxxx xxxx xxxxxxxx xxxx xxxx
首先是发送 32 位“0”的起始帧数据,然后接着发送 32 位的灰度数据,而这 32 位的灰度数据中分为 3 部分组成,包括“标志位”、“校验数据”、以及“24 位的 RGB 数据”。其中高两位(bit31 和 bit30)为“1”的标志位,bit29~24 位为 BGR 三种颜色灰度数据高两位(B7、 B6、 G7、 G6、 R7、 R6)的反码,反码就是正常颜色值的取反值,最后 bit23~0 位为 BGR 共 24 位的灰度数据。灰度级数据传输时需高位先入,并且按照蓝绿红(BGR)顺序发送。在通讯过程中时钟线和数据线时间建议工作参数如表所示。
参数 | 符号 | 范围 | 单位 |
数据时钟频率 | FCLK | 0-8 | mhz |
时钟高电平宽度 | TCLKH | >30 | ns |
时钟低电平宽度 | TCLKL | >30 | ns |
数据建立时间 | TSETUP | >10 | ns |
数据保持时间 | THOLD | >5 | ns |
根据上述P9813的时序要求,时钟高低电平的最小延时是30ns,因此生成时钟的函数如下:
/**
* @retval None
*/
void clk_produce(void)
{
SCL_LOW; // SCL=0
delay_us(50);
SCL_HIGH; // SCL=1
delay_us(50);
}
接下来就是RGB控制的核心代码:
/**
* @brief invert the grey value of the first two bits
* @param dat
* @retval tmp
*/
uint8_t take_anti_code(uint8_tdat)
{
uint8_t tmp = 0;
tmp=((~dat) & 0xC0)>>6;
return tmp;
}
/**
* @brief send gray data
* @param dx
* @retval None
*/
void dat_send(uint32_t dx)
{
uint8_t i;
for (i=0; i<32; i++)
{
if ((dx & 0x80000000) != 0)
{
SDA_HIGH; // SDA=1;
}
else
{
SDA_LOW; // SDA=0;
}
dx <<= 1;
clk_produce();
}
}
/**
* @brief data processing
* @param r, g ,b
* @retval None
*/
voiddata_deal_with_and_send(uint8_t r, uint8_t g, uint8_t b)
{
uint32_t dx = 0;
dx |= (uint32_t)0x03 << 30; // The front of the two bits 1 isflag bits
dx |= (uint32_t)take_anti_code(b) <<28;
dx |= (uint32_t)take_anti_code(g) <<26;
dx |= (uint32_t)take_anti_code(r) <<24;
dx |= (uint32_t)b << 16;
dx |=(uint32_t)g << 8;
dx |= r;
dat_send(dx);
}
take_anti_code()函数实现了反转 RGB 灰度数据前高两位 bit7 和 bit6。
dat_send()函数,该函数实现发送灰度数据,从高位开始发送,共发送 32 位的数据。
data_deal_with_and_send()函数,该函数实现了通讯协议中 32bit 灰度数据的组成,先把 bit31 和 bit30 位设置为“1”标志位,接着让 bit29~24 位按 B、 G、 R 顺序反转BGR 三组灰度数据的高两位(B7、 B6、 G7、 G6、 R7、 R6),然后剩余的 bit23~0 也是按B、 G、 R 的顺序拼接 BGR 三组的 8 位灰度数据, 32bit 灰度数据组成完成后,最后调用dat_send()函数发送 32bit 数据。
最后就是RGB的初始化和控制了。
/**
* @brief rgb init
* @param r, g ,b
* @retval None
*/
void rgb_led_init(void)
{
send_32_zero();
data_deal_with_and_send(0,0,0);
data_deal_with_and_send(0,0,0);
}
/**
* @brief rgb reg control
* @param r, g ,b
* @retval None
*/
void led_rgb_control(uint8_t r,uint8_t g, uint8_t b)
{
send_32_zero();
data_deal_with_and_send(r, g, b);
data_deal_with_and_send(r, g, b);
}
rgb_led_init()函数用于RGB的初始化,
led_rgb_control()函数实现 RGB 灯颜色显示,通过输入 R、 G、 B 颜色值数据(范围 0~255)就可以显示我们需要的颜色。
2.2.3直流电动驱动开发在物联网中,电机的用处有很多,在智能家居系统中可以控制窗帘,在自动灌溉系统可以控制蓄水引擎等场景,GoKit3采用 L9110 进行驱动。
L9110芯片有两个 TTL/CMOS兼容电平的输入,具有良好的抗干扰性;两个输出端能直接驱动电机的正反向运动,它具有较大的电流驱动能力,每通道能通过 800mA 的持续电流,峰值电流能力可达 1.5A;同时它具有较低的输出饱和压降;内置的钳位二极管能释放感性负载的反向冲击电流,使它在驱动继电器、 直流电机、 步进电机或开关功率管的使用上安全可靠,L9110输入输出波形如下。
本文将使用PWM驱动L9110,从而控制直流电机,其硬件威廉希尔官方网站
如下:
L9110控制电机速度和方向非常简单,按L9110输入输出波形,只要向输入端IA/IB输入高电平则为转动,IA 正转,IB为反转。
速度是通过调幅PWM 信号进行控制,也就是对IA/IB 写入 1~255 的速度范围则可控制电机的转速。
我们这里使用TIM3来输出PWM,关于定时器的使用,请参看笔者博文:
定时器3设置的分频系数是9,通过分频后得到的时钟是7.2MHz。定时器周期设置的是7199,因此产生中断的频率为:7.2MHz/7200=0.1KHz。
TIM3的脉冲计数器 TIMx_CNT 上限设置为7199, TIMx_CCR 的值时,就会在通道中输出低电平,反之亦然,因此只需要改变TIMx_CCR,就能改变PWM的占空比,从而控制电机的转动。
void motor_control(uint8_tm1,uint8_t m2)
{
uint16_t temp = (MOTOR_ARR + 1) /MOTOR_MAX;
__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1, m1 * temp);
__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_2, m1 * temp);
}
motor_control()函数有两个参数,分别控制IA 和IB,只需要设置不同的数值即可。
2.2.4 DHT11驱动开发DHT11 数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有极高的可靠性与卓越的长期稳定性。
关于DHT11的详细讲解请参看笔者博文:
DHT11 模块的数据管脚用于 MCU 与 DHT11 之间的通讯和同步,采用单总线数据格式,一次通讯时间 4ms 左右,一次完整的数据传输为 40bit,高位先出,数据分小数部分和整数部分,具体格式是:
8bit 湿度整数数据+8bit 湿度小数数据+8bi 温度整数数据+8bit 温度小数数据 +8bit校验和数据传送正确时校验和数据等于 8bit 湿度整数数据+8bit 湿度小数数据+8bi 温度整数数据+8bit 温度小数数据所得结果的末 8 位。
传感器数据输出的是未编码的二进制数据。数据(湿度、温度、整数、小数)之间应该分开处理。例如,某次从 DHT11 读到的数据如下所示。
由以上数据就可得到湿度和温度的值,计算方法:
湿度= byte4 .byte3=45.0 (%RH)
温度= byte2 .byte1=28.0 ( ℃)
校验= byte4+ byte3+byte2+ byte1=73(=湿度+温度)(校验正确)
可以看出, DHT11 的数据格式是十分简单的, DHT11 和 MCU 的一次通信最大为 3ms 左右,建议主机连续读取时间间隔不要小于 100ms。
下面,介绍一下 DHT11 的传输时序。
1.主机与DHT11通讯流程
主机MCU 发送一次开始信号后,DHT11 从低功耗模式转换到高速模式,等待主机开始信号结束后,即:拉低数据线,保持至少 18ms时间,然后拉高数据线 20~40us时间。DHT11 发送响应信号,送出 40bit 的数据,并触发一次信号采集,用户可选择读取部分数据。正常情况, DHT11 会拉低数据线,保持80us时间,作为响应信号,然后 DHT11 拉高数据线,保持80us时间后,开始输出数据。DHT11 接收到开始信号触发一次温湿度采集,如果没有接收到主机发送开始信号,DHT11 不会主动进行温湿度采集。采集数据后转换到低速模式。
2.主机复位信号和 DHT11 响应信号
总线空闲状态为高电平,主机把总线拉低等待 DHT11 响应,主机把总线拉低必须大于 18 毫秒,保证 DHT11 能检测到起始信号。DHT11 接收到主机的开始信号后,等待主机开始信号结束,然后发送 80us 低电平响应信号。主机发送开始信号结束,延时等待 80us 后,读取 DHT11 的响应信号,主机发送开始信号后,可以切换到输入模式,或者输出高电平均可,总线由上拉电阻拉高,如图中黑色线所示
总线为低电平,说明 DHT11 发送响应信号,DHT11 发送响应信号后,再把总线拉高 80us,准备发送数据,每一 bit 数据都以 50us 低电平时隙开始,高电平的长短定了数据位是 0 还是 1。格式如图中灰色线所示。如果读取响应信号为高电平,则 DHT11 没有响应,请检查线路是否连接正常。当最后 1bit 数据传送完毕后,DHT11 拉低总线 50us,随后总线由上拉电阻拉高进入空闲状态。
3.数字‘ 0’信号表示方法
数字 0 信号表示方法如图所示
4.数字‘ 1’信号表示方法
数字 1 信号表示方法如图所示
这里我们可以看出,数字“0”和数字“1”不同的地方在于高电平的时间不同,这也是读取数据的关键所在。
DHT11模块相关威廉希尔官方网站
途如下图所示:
传感器模块是单总线通信,单总线通常要求外接一个上拉电阻,这样,当总线闲置时,总线上始终是高电平。
/**
* @brief 从DHT11读取一个字节,MSB先行
* @param None
* @retval byte
*/
static uint8_t dht11_read_byte (void )
{
uint8_ti, byte=0;
for(i=0;i<8;i++)
{
/*每bit以50us低电平标置开始,轮询直到从机发出 的50us 低电平 结束*/
while(DHT11_Dout_IN()== GPIO_PIN_RESET);
/*DHT11以26~28us的高电平表示“0”,以70us高电平表示“1”,
*通过检测 x us后的电平即可区别这两个状 ,x 即下面的延时
*/
delay_us(40);//延时x us 这个延时需要大于数据0持续的时间即可
if(DHT11_Dout_IN()==GPIO_PIN_SET)/*x us后仍为高电平表示数据“1” */
{
/*等待数据1的高电平结束 */
while(DHT11_Dout_IN()==GPIO_PIN_SET);
byte|=(uint8_t)(0x01<<(7-i)); //把第7-i位置1,MSB先行
}
else // x us后为低电平表示数据“0”
{
byte&=(uint8_t)~(0x01<<(7-i));//把第7-i位置0,MSB先行
}
}
returnbyte;
}
DHT11 接收到主机的开始信号后,等待主机开始信号结束,然后发送 80us 低电平响应信号。主机发送开始信号结束,延时等待 80us 后, DHT11 开始输出数据。每一 bit 数据都以 50us 低电平时隙开始,高电平的长短定了数据位是 0 还是 1。
2.2.5应用开发根据上一章的讲解,完成了底层驱动后,用户只需要调用相应的 API 接口或添加相应的逻辑处理即可。
需要开发的部分为:
A. 下行处理: LED 灯开关、电机转速控制。
B. 上行处理: 温湿度数据采集。
C. 配置处理: 配置入网及恢复出厂设置。
下面一一将讲解。
首先是初始化外设,在 Gizwits 目录下的gizwits_product.c 文件中userInit()函数中。
void userInit(void)
{
memset((uint8_t*)¤tDataPoint, 0,sizeof(dataPoint_t));
delay_init(72); // 延时 初始化
rgb_led_init(); // RGB LED init
dht11_init(); //init dht11
motor_init(); // 电机初始化
motor_status(0); // 电机转速初始化
currentDataPoint.valueLED_OnOff = 0;
currentDataPoint.valueMotor_Speed = 0;
currentDataPoint.valueTemperature = 0;
currentDataPoint.valueHumidity = 0;
}
在 Gizwits 目录下的gizwits_product.c 文件中的gizwitsEventProcess()函数中处理相应事件。
int8_tgizwitsEventProcess(eventInfo_t *info, uint8_t *gizdata, uint32_t len)
{
uint8_t i = 0;
dataPoint_t *dataPointPtr = (dataPoint_t *)gizdata;
moduleStatusInfo_t *wifiData = (moduleStatusInfo_t *)gizdata;
protocolTime_t *ptime = (protocolTime_t *)gizdata;
#if MODULE_TYPE
gprsInfo_t *gprsInfoData = (gprsInfo_t *)gizdata;
#else
moduleInfo_t *ptModuleInfo = (moduleInfo_t *)gizdata;
#endif
if((NULL == info) || (NULL == gizdata))
{
return -1;
}
for(i=0; inum; i++)
{
switch(info->event)
{
case EVENT_LED_OnOff:
currentDataPoint.valueLED_OnOff =dataPointPtr->valueLED_OnOff;
GIZWITS_LOG("Evt: EVENT_LED_OnOff%d n", currentDataPoint.valueLED_OnOff);
if(0x01 ==currentDataPoint.valueLED_OnOff)
{
//user handle
led_rgb_control(0, 254, 0);
}
else
{
//user handle
led_rgb_control(0, 0, 0);
}
break;
case EVENT_Motor_Speed:
currentDataPoint.valueMotor_Speed =dataPointPtr->valueMotor_Speed;
GIZWITS_LOG("Evt:EVENT_Motor_Speed%dn",currentDataPoint.valueMotor_Speed);
//user handle
motor_status(currentDataPoint.valueMotor_Speed);
break;
case WIFI_SOFTAP:
break;
case WIFI_AIRLINK:
break;
case WIFI_STATION:
break;
case WIFI_CON_ROUTER:
break;
case WIFI_DISCON_ROUTER:
break;
case WIFI_CON_M2M:
break;
case WIFI_DISCON_M2M:
break;
case WIFI_RSSI:
GIZWITS_LOG("RSSI %dn",wifiData->rssi);
break;
case TRANSPARENT_DATA:
GIZWITS_LOG("TRANSPARENT_DATAn");
//user handle , Fetch data from [data], size is [len]
break;
case WIFI_NTP:
GIZWITS_LOG("WIFI_NTP : [%d-%d-%d%02d:%02d:%02d][%d]n",ptime->year,ptime->month,ptime->day,ptime->hour,ptime->minute,ptime->second,ptime->ntp);
break;
case MODULE_INFO:
GIZWITS_LOG("MODULE INFO...n");
#if MODULE_TYPE
GIZWITS_LOG("GPRS MODULE...n");
//Format By gprsInfo_t
#else
GIZWITS_LOG("WIF MODULE...n");
//Format By moduleInfo_t
GIZWITS_LOG("moduleType : [%d]n",ptModuleInfo->moduleType);
#endif
break;
default:
break;
}
}
return 0;
}
在 user 目录下gizwits_product.c文件中的 userHandle()函数中实现传感器数据采集,用户只需并将采集到的数值赋值给对应用户区的设备状态结构体数据位即可。
void userHandle(void)
{
DHT11_Data_TypeDef DHT11_Data;
uint8_t ret = 0;
staticuint32_t thLastTimer = 0;
//温湿度传感器数据获取
if((gizGetTimerCount()-thLastTimer) > 2000) //上报间隔2S
{
ret= dht11_read_temp_and_humidity(&DHT11_Data);
if(ret != 0)
{
GIZWITS_LOG("Failedto read DHT11 [%d]n", ret);
}
else
{
currentDataPoint.valueTemperature= DHT11_Data.temp_int;
currentDataPoint.valueHumidity= DHT11_Data.humi_int;
}
thLastTimer = gizGetTimerCount();
}
}
值得注意的是,关于驱动的头文件需要包含到gizwits_product.c中,请根据实际情况添加。
最后编译完成后将固件下载到MCU中,最后打开App,即可控制相应的设备。
值得注意的是,不管使用WiFi还是使用4G,MCU端的代码都是意昂的,无需更改,App端只需绑定对应的通信设备。
最新后效果如下: