【野火】瑞萨RA MCU创意氛围赛+无线环境检测小车
本帖最后由 wuboy19 于 2023-8-22 18:23 编辑【野火】瑞萨RA MCU创意氛围赛+无线环境检测小车
一、项目简介
本项目旨在基于启明6M5开发板设计一个无线环境监测小车系统,用于监测实验室环境,用户能够通过蓝牙对小车进行控制,并通过onenet云平台进行查看当前环境数据以及历史数据,具体实现功能如下:
[*]利用启明6M5开发板完成对温湿度DHT11的温湿度读取。
[*]利用启明6M5开发板完成对板载esp8266与onenet云平台的数据传输。
[*]利用启明6M5开发板完成对麦克拉姆轮小车的控制以及蓝牙控制。
[*]完成onenet云平台的mqtt协议接入。并可视化显示进行部署,实时显示小车温湿度数据。
二、项目系统组成整个系统框图:
2.1硬件介绍 启明6M5开发板
[*]采用 Arm Cortex-M33 内核的瑞萨 RA 系列 32 位 MCU;
[*]主芯片为的R7FA6M5BH3CFC,主高达200MHz,2MB的代码闪存,8KB数据闪存,以及512KB的带奇偶校验/ECC的SRAM;
[*]LQFP封装,IO口128个,底板引出未使用的IO口47个。
板子实物图如下:
本次项目中使用到了外设如下:
[*]GPT2、GPT4、GPT6、GPT8用于输出思路PWM输出用于控制麦克拉姆轮的四个轮子的使能端;
[*]SCI4、SCI9用于蓝牙通讯和板载esp8266的通信;
[*]P400用于LED指示程序运行;
[*]GPIO用到的如下表格:
小车GPIO以及GPT绑定的引脚使用
l298n
麦克拉姆轮运动可以参考:https://blog.csdn.net/weixin_42108484/article/details/122090548?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522169260570916800188577262%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=169260570916800188577262&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~baidu_landing_v2~default-1-122090548-null-null.142%5Ev93%5EchatgptT3_2&utm_term=%E9%BA%A6%E5%85%8B%E6%8B%89%E5%A7%86%E8%BD%AE&spm=1018.2226.3001.4187
RA smart 配置如下:
onenet配置
可以参考链接1:https://blog.csdn.net/m0_37738838/article/details/85012065?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522169260830116800222878194%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=169260830116800222878194&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-85012065-null-null.142%5Ev93%5EchatgptT3_2&utm_term=onenet%20esp8266%20arduino&spm=1018.2226.3001.4187
可以参考链接2:https://blog.csdn.net/weixin_51651698/article/details/131262456?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_utm_term~default-4-131262456-blog-85012065.235%5Ev38%5Epc_relevant_anti_vip_base&spm=1001.2101.3001.4242.3&utm_relevant_index=7
DHT11模块
DHT11 数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器,内部由一个 8 位单片机控制一个电阻式感湿元件和一个 NTC 测温元件。DHT11 虽然也是采用单总线协议,但是该协议与 DS18B20 的单总线协议稍微有些不同之处。
相比于 DS18B20 只能测量温度,DHT11 既能检测温度又能检测湿度,不过 DHT11 的精度和测量范围都要低于 DS18B20,其温度测量范围为 0~50℃,误差在±2℃;湿度的测量范围为 20%~90%RH(Relative Humidity 相对湿度—指空气中水汽压与饱和水汽压的百分比),误差在±5%RH。DHT11 电路很简单,只需要将 DATA 引脚连接单片机的一个 I/O 即可,不过该引脚需要上拉一个 5K 的电阻,DHT11 的供电电压为 3~5.5V。
https://www.firebbs.cn/forum.php?mod=image&aid=38860&size=300x300&key=ec700c35f23be853&nocache=yes&type=fixnone
6M5接线DHT11
三、项目系统实现
(1)PWM产生
小车需要四路PWM信号,来控制L298N的使能端,来控制电机转速。PWM的频率配置成20Khz是默认电机的频率,然后通过控制占空比来达到控制电机转速/
PWM的核心代码
#include "bsp_gpt_pwm_output.h"
/*当前选用PWM输出引脚 GTIOC6B P600 */
/* GPT初始化函数 */
void GPT6_PWM_Init(void)
{
/* 初始化 GPT 模块 */
R_GPT_Open(&g_timer_gpt6_ctrl, &g_timer_gpt6_cfg);
/* 启动 GPT 定时器 */
R_GPT_Start(&g_timer_gpt6_ctrl);
/* 重新设置占空比为 80% */
// GPT_PWM_SetDuty(80);
}
void GPT4_PWM_Init(void)
{
/* 初始化 GPT 模块 */
R_GPT_Open(&g_timer_gpt4_ctrl, &g_timer_gpt4_cfg);
/* 启动 GPT 定时器 */
R_GPT_Start(&g_timer_gpt4_ctrl);
/* 重新设置占空比为 80% */
// GPT_PWM_SetDuty(80);
}
void GPT8_PWM_Init(void)
{
/* 初始化 GPT 模块 */
R_GPT_Open(&g_timer_gpt8_ctrl, &g_timer_gpt8_cfg);
/* 启动 GPT 定时器 */
R_GPT_Start(&g_timer_gpt8_ctrl);
/* 重新设置占空比为 80% */
// GPT_PWM_SetDuty(80);
}
void GPT2_PWM_Init(void)
{
/* 初始化 GPT 模块 */
R_GPT_Open(&g_timer_gpt2_ctrl, &g_timer_gpt2_cfg);
/* 启动 GPT 定时器 */
R_GPT_Start(&g_timer_gpt2_ctrl);
/* 重新设置占空比为 80% */
// GPT_PWM_SetDuty(80);
}
/** 自定义函数:设置PWM占空比
@param duty 占空比范围:0~100 %
*/
void GPT6_PWM_SetDuty(uint8_t duty)
{
timer_info_t info;
uint32_t current_period_counts;
uint32_t duty_cycle_counts;
if (duty > 100)
duty = 100; //限制占空比范围:0~100
/* 获得GPT的信息 */
R_GPT_InfoGet(&g_timer_gpt6_ctrl, &info);
/* 获得计时器一个周期需要的计数次数 */
current_period_counts = info.period_counts;
/* 根据占空比和一个周期的计数次数计算GTCCR寄存器的值 */
duty_cycle_counts = (uint32_t)(((uint64_t) current_period_counts * duty) / 100);
/* 最后调用FSP库函数设置占空比 */
R_GPT_DutyCycleSet(&g_timer_gpt6_ctrl, duty_cycle_counts, GPT_IO_PIN_GTIOCB);
}
void GPT4_PWM_SetDuty(uint8_t duty)
{
timer_info_t info;
uint32_t current_period_counts;
uint32_t duty_cycle_counts;
if (duty > 100)
duty = 100; //限制占空比范围:0~100
/* 获得GPT的信息 */
R_GPT_InfoGet(&g_timer_gpt4_ctrl, &info);
/* 获得计时器一个周期需要的计数次数 */
current_period_counts = info.period_counts;
/* 根据占空比和一个周期的计数次数计算GTCCR寄存器的值 */
duty_cycle_counts = (uint32_t)(((uint64_t) current_period_counts * duty) / 100);
/* 最后调用FSP库函数设置占空比 */
R_GPT_DutyCycleSet(&g_timer_gpt4_ctrl, duty_cycle_counts, GPT_IO_PIN_GTIOCB);
}
void GPT8_PWM_SetDuty(uint8_t duty)
{
timer_info_t info;
uint32_t current_period_counts;
uint32_t duty_cycle_counts;
if (duty > 100)
duty = 100; //限制占空比范围:0~100
/* 获得GPT的信息 */
R_GPT_InfoGet(&g_timer_gpt8_ctrl, &info);
/* 获得计时器一个周期需要的计数次数 */
current_period_counts = info.period_counts;
/* 根据占空比和一个周期的计数次数计算GTCCR寄存器的值 */
duty_cycle_counts = (uint32_t)(((uint64_t) current_period_counts * duty) / 100);
/* 最后调用FSP库函数设置占空比 */
R_GPT_DutyCycleSet(&g_timer_gpt8_ctrl, duty_cycle_counts, GPT_IO_PIN_GTIOCB);
}
void GPT2_PWM_SetDuty(uint8_t duty)
{
timer_info_t info;
uint32_t current_period_counts;
uint32_t duty_cycle_counts;
if (duty > 100)
duty = 100; //限制占空比范围:0~100
/* 获得GPT的信息 */
R_GPT_InfoGet(&g_timer_gpt2_ctrl, &info);
/* 获得计时器一个周期需要的计数次数 */
current_period_counts = info.period_counts;
/* 根据占空比和一个周期的计数次数计算GTCCR寄存器的值 */
duty_cycle_counts = (uint32_t)(((uint64_t) current_period_counts * duty) / 100);
/* 最后调用FSP库函数设置占空比 */
R_GPT_DutyCycleSet(&g_timer_gpt2_ctrl, duty_cycle_counts, GPT_IO_PIN_GTIOCA);
}
(2)串口收发(本作者将stm32的sendstring函数进行一直,实现发送任意长度字符串)
蓝牙串口:该串口核心就是接收蓝牙发送的数据,产生接收中断后对其接收到的字符进行判断。
#include "bsp_debug_uart.h"
#include "string.h"
#include "stdlib.h"
#include "led/bsp_led.h"
#include "gpt/bsp_gpt_pwm_output.h"
/* 调试串口 UART4 初始化 */
void Debug_UART4_Init(void)
{
fsp_err_t err = FSP_SUCCESS;
err = R_SCI_UART_Open (&g_uart4_ctrl, &g_uart4_cfg);
assert(FSP_SUCCESS == err);
}
/* 发送完成标志 */
volatile bool uart_send_complete_flag = false;
//qianmian
//6 4
//8 2
/* 串口中断回调 */
//void GPT6_FRONT();
//void GPT6_BEHIND();
//void GPT6_STOP();
void debug_uart4_callback (uart_callback_args_t * p_args)
{
switch (p_args->event)
{
case UART_EVENT_RX_CHAR:
{
switch (p_args->data)
{
case '3':
//前进
GPT6_FRONT();
GPT4_FRONT();
GPT8_FRONT();
GPT2_FRONT();
break;
case '4':
//后退
GPT6_BEHIND();
GPT4_BEHIND();
GPT8_BEHIND();
GPT2_BEHIND();
break;
case '5':
//停止
GPT6_STOP();
GPT4_STOP();
GPT8_STOP();
GPT2_STOP();
break;
case '6':
//左旋
GPT6_BEHIND();
GPT4_FRONT();
GPT8_FRONT();
GPT2_BEHIND();
break;
case '7':
//右旋
GPT6_FRONT();
GPT4_BEHIND();
GPT8_BEHIND();
GPT2_FRONT();
break;
default:
break;
}
break;
}
case UART_EVENT_TX_COMPLETE:
{
uart_send_complete_flag = true;
break;
}
default:
break;
}
}
void zero_str(char *str,uint16_t index)
{
int i;
for(i=0;i<index;i++)
{
*(str+index)='\0';
}
}
/* 重定向 printf 输出 */
#if defined __GNUC__ && !defined __clang__
int _write(int fd, char *pBuffer, int size); //防止编译警告
int _write(int fd, char *pBuffer, int size)
{
(void)fd;
R_SCI_UART_Write(&g_uart4_ctrl, (uint8_t *)pBuffer, (uint32_t)size);
while(uart_send_complete_flag == false);
uart_send_complete_flag = false;
return size;
}
#else
int fputc(int ch, FILE *f)
{
(void)f;
R_SCI_UART_Write(&g_uart4_ctrl, (uint8_t *)&ch, 1);
while(uart_send_complete_flag == false);
uart_send_complete_flag = false;
return ch;
}
#endif
onenet串口:该串口在蓝牙串口基础上增加发送字符串函数,由于开始时候为了方便测试。默认printf给蓝牙串口,也就是本机串口使用,所以自行编写串口发送,两者不要同时调用会有bug,虽然配置了优先级。
#include "bsp_onenet.h"
/* 调试串口 UART9 初始化 */
void onenet_UART9_Init(void)
{
fsp_err_t err = FSP_SUCCESS;
err = R_SCI_UART_Open (&g_uart9_ctrl, &g_uart9_cfg);
assert(FSP_SUCCESS == err);
}
/* 发送完成标志 */
volatile bool onenet_uart_send_complete_flag = false;
/* 串口中断回调 */
void onenet_uart9_callback (uart_callback_args_t * p_args)
{
switch (p_args->event)
{
case UART_EVENT_RX_CHAR:
{
/* 把串口接收到的数据发送回去 */
R_SCI_UART_Write(&g_uart9_ctrl, (uint8_t *)&(p_args->data), 1);
break;
}
case UART_EVENT_TX_COMPLETE:
{
onenet_uart_send_complete_flag = true;
break;
}
default:
break;
}
}
void Usart_SendByte( uart_ctrl_t * const pUSARTx, uint8_t ch)
{
/* 发送一个字节数据到USART */
R_SCI_UART_Write(pUSARTx, (uint8_t *)&ch, 1);
/* 等待发送数据寄存器为空 */
while(onenet_uart_send_complete_flag == false)
{
}
onenet_uart_send_complete_flag = false;
}
/*****************发送字符串 **********************/
void Usart_SendString( uart_ctrl_t * const pUSARTx, char *str)
{
unsigned int k=0;
do
{
Usart_SendByte( pUSARTx, *(str + k) );
k++;
} while(*(str + k)!='\0');
/* 等待发送完成 */
}
DHT11读取温湿度:DHT11就是单总线读取,读取后通过格式化字符串写入。
#include "bsp_dht11.h" // Device header
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "debug_uart/bsp_debug_uart.h"
DHT11_Data_TypeDef DHT11_Data_my;
float temperature;
float hum;
char T_Data={0};
char H_Data={0};
char dis_str={0};
/* DHT11初始化函数 */
void DHT11_Init(void)
{
/* 初始化配置引脚(这里重复初始化了,可以注释掉) */
R_IOPORT_Open (&g_ioport_ctrl, g_ioport.p_cfg);
}
void DHT11_DELAY_US(uint32_t delay)
{
R_BSP_SoftwareDelay(delay, BSP_DELAY_UNITS_MICROSECONDS);
}
void DHT11_DELAY_MS(uint32_t delay)
{
R_BSP_SoftwareDelay(delay, BSP_DELAY_UNITS_MILLISECONDS);
}
//主机发送开始信号
void DHT11_Start(void)
{
DHT_HIGH; //先拉高
DHT11_DELAY_US(30);
DHT_LOW; //拉低电平至少18us
DHT11_DELAY_MS(20);
DHT_HIGH; //拉高电平20~40us
DHT11_DELAY_US(30);
}
/*
* 从DHT11读取一个字节,MSB先行
*/
static uint8_t Read_Byte(void)
{
uint8_t i, temp=0;
for(i=0;i<8;i++)
{
/*每bit以50us低电平标置开始,轮询直到从机发出 的50us 低电平 结束*/
while(Read_Data == Bit_RESET);
/*DHT11 以26~28us的高电平表示“0”,以70us高电平表示“1”,
*通过检测 x us后的电平即可区别这两个状 ,x 即下面的延时
*/
DHT11_DELAY_US(40); //延时x us 这个延时需要大于数据0持续的时间即可
if(Read_Data == Bit_SET)/* x us后仍为高电平表示数据“1” */
{
/* 等待数据1的高电平结束 */
while( Read_Data ==Bit_SET);
temp|=(uint8_t)(0x01<<(7-i));//把第7-i位置1,MSB先行
}
else // x us后为低电平表示数据“0”
{
temp&=(uint8_t)~(0x01<<(7-i)); //把第7-i位置0,MSB先行
}
}
return temp;
}
/*
* 一次完整的数据传输为40bit,高位先出
* 8bit 湿度整数 + 8bit 湿度小数 + 8bit 温度整数 + 8bit 温度小数 + 8bit 校验和
*/
uint8_t Read_DHT11(DHT11_Data_TypeDef *DHT11_Data)
{
uint16_t count;
DHT11_Start();
DHT_HIGH; //拉高电平
/*判断从机是否有低电平响应信号 如不响应则跳出,响应则向下运行*/
if( Read_Data == Bit_RESET)
{
count=0;
/*轮询直到从机发出 的80us 低电平 响应信号结束*/
while( Read_Data ==Bit_RESET)
{
count++;
if(count>1000)
return 0;
DHT11_DELAY_US(10);
}
count=0;
/*轮询直到从机发出的 80us 高电平 标置信号结束*/
while( Read_Data==Bit_SET)
{
count++;
if(count>1000)
return 0;
DHT11_DELAY_US(10);
}
/*开始接收数据*/
DHT11_Data->humi_int= Read_Byte();
DHT11_Data->humi_deci= Read_Byte();
DHT11_Data->temp_int= Read_Byte();
DHT11_Data->temp_deci= Read_Byte();
DHT11_Data->check_sum= Read_Byte();
DHT_LOW;
DHT11_DELAY_US(55);
DHT_HIGH;
/*检查读取的数据是否正确*/
if(DHT11_Data->check_sum == DHT11_Data->humi_int + DHT11_Data->humi_deci + DHT11_Data->temp_int+ DHT11_Data->temp_deci)
return 1;
else
return 0;
}
else
{
return 0;
}
}
void Read_ALL(void)
{
if( Read_DHT11 ( & DHT11_Data_my ) == 1)
{
sprintf(T_Data,"%d.%d",DHT11_Data_my.temp_int,DHT11_Data_my.temp_deci);
sprintf(H_Data,"%d.%d",DHT11_Data_my.humi_int,DHT11_Data_my.humi_deci);
temperature=atof(T_Data);
hum=atof(H_Data);
// printf("T:%sH:%s\n",T_Data,H_Data);
// delay_ms(50);
// printf("i:%d\n",DHT11_Data.temp_int);
// printf("d:%d\n",DHT11_Data.temp_deci);
}
}
esp8266连接onenet:连接onenet主要是通过多协议接入的方法,通过mqtt协议对数据进行传输,然后通过onenet平台的可视化进行对数据显示。
#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
String rx_data;
/********************修改***************************/
#define wifi_ssid "project_my" //wifi名称
#define wifi_password "12345678" //wifi密码
/********************不修改***************************/
#define mqtt_server "183.230.40.39"/*OneNet-MQTT IP不修改*/
#define mqtt_port 6002/*OneNet-MQTT port不修改*/
/********************修改***************************/
#define mqtt_devid ""/*OneNet-MQTT DeviceID设备ID*/
#define mqtt_pubid ""/*OneNet-MQTT ProductID产品ID*/
#define mqtt_password ""/*OneNet-MQTT APIkeymaster apikey*/
/********************不修改***************************/
WiFiClient espClient;
PubSubClient client(espClient);
long last_time = 0;
unsigned short json_len = 0;
/********************修改***************************/
char dataDHT11[] = "{\"temp\":%.2f, \"hum\":%.2f, \"pressure\":%d}";//dataDHT11位输出的名称temp、hump为onenet上数据流名称 %0.2f为数据类型
char msgJson;//根据数据总名称+数据长度而定,长的话可以改
char msg_buf;//根据数据总名称+数据长度而定,长的话可以改
float temperature;//传感器数据,这里使用假数据,视自己情况而定
float hum;
int pressure;
/********************不修改***************************/
String getstringfromdata(String source_str,String str_start,String str_end)//字符串提取函数
{
String getstring_temp="";
if(source_str.equals(""))
{
return "";
}
else
{
getstring_temp=source_str.substring(source_str.indexOf(str_start)+1,source_str.indexOf(str_end));
return getstring_temp;
}
}
void setup()
{
Serial.begin(115200);
client.setServer(mqtt_server, mqtt_port);
client.setCallback(mqtt_callback);
}
void setup_wifi()
{
Serial.println();
Serial.print("Connecting to ");
Serial.println(wifi_ssid);
WiFi.begin(wifi_ssid, wifi_password);
Serial.println("--WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
void mqtt_callback(char* topic, byte* payload, unsigned int length)//
{
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
for (int i = 0; i < length; i++)
{
Serial.print((char)payload);
}
Serial.println();
DynamicJsonDocument doc(1024);
deserializeJson(doc, payload);
JsonObject obj = doc.as<JsonObject>();
}
void reconnect()
{
while (!client.connected())
{
Serial.print("Attempting MQTT connection...");
if (client.connect(mqtt_devid, mqtt_pubid, mqtt_password))//连接MQTT服务器
{
Serial.println("connected");
}
else
{
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 2s");
delay(2000);
}
}
}
void loop()
{
/********************修改***************************/
delay(200);
/********************不修改***************************/
if(WiFi.status() != WL_CONNECTED)
{
setup_wifi();
}
if (!client.connected())
{
reconnect();
}
client.loop();
long now = millis();
if (now - last_time > 3000)
{
last_time = now;
/********************修改***************************/
// dataDHT11为setup()之前设置的要上传的数组,temperature, humidity,press,ax为%0.2f替换的内容100为数组最大长度,如果数据多,就改大一点
snprintf(msgJson, 100, dataDHT11, temperature, hum,pressure);
/********************不修改***************************/
Serial.print(msgJson);
memset(msg_buf, 0, 200);
json_len = strlen(msgJson); //packet length count the end char '\0'
msg_buf = char(0x03); //palyLoad packet byte 1, one_net mqtt Publish packet payload byte 1, type3 , json type2
msg_buf = char(json_len >> 8); //high 8 bits of json_len (16bits as short int type)
msg_buf = char(json_len & 0xff); //low 8 bits of json_len (16bits as short int type)
memcpy(msg_buf + 3, msgJson, strlen(msgJson));
Serial.print("Lngth of published message: ");
client.publish("$dp", (uint8_t*)msg_buf, 3 + strlen(msgJson));// msg_buf as payload length which may have a "0x00"byte
Serial.println(strlen(msgJson));
}
}
//串口中断入口
void serialEvent()
{
rx_data="";
while (Serial.available())
{
rx_data += char(Serial.read());
delay(2); //这里不能去掉,要给串口处理数据的时间
}
Serial.println(rx_data);
temperature=(getstringfromdata(rx_data,"a","b")).toFloat();
hum=(getstringfromdata(rx_data,"b","c")).toFloat();
pressure=(getstringfromdata(rx_data,"c","d")).toInt();
}
四、项目成果展示
(1)开始时候是使用灯来看四路PWM是否输出,以及对应IN1和IN2是否输出。(确认无误后将其和电机对应使能和IN脚相接)
(2)onenet实时显示温湿度数据
(3)蓝牙助手界面
(4)整体实物以及运动视频
视频链接:https://www.bilibili.com/video/BV13u4y1i7zj/
页:
[1]