RT-Thread 基础项目实战(F407ZGT6)
1. 基于STM32芯片创建HelloWorld工程
下载安装RT-Thread Studio、新建项目、构建项目、烧录程序、输出HelloWorld、点亮LED灯等均在上一篇博客中已经写过,这里根据参考资料1的步骤写一下正点原子探索者STM32F407ZGT6怎么修改系统时钟为外部高速晶振HSE。
上一篇博客:https://blog.csdn.net/m0_53488817/article/details/118458086
关于改用外部晶振时钟,参考博客中是这么写的:
在这里,根据正点原子探索者F407进行一些修正:
1.1 SystemClock_config函数
首先,该函数的位置并不在board.c中,在新建项目的时候可以看到如下的提示:
在左侧项目结构中选择drivers文件夹,其中有个drv_clk.c文件,双击打开如下:
这样我们就找到了SystemClock_config函数,对其进行如下修改:
重新构建项目后,烧录程序,可以看到开发板上的红色led灯每0.5秒循环亮灭。
备注:
①不要只顾着修改等号右边,等号左侧的变量名也要注意修改
②PLLM这一项,参考资料中写的为1,我将其改为1之后烧录程序led灯是没有反应的,打开Terminal,可以看到串口也没有打印数据,PLLM的定义如下:
输入1并没有超过该范围,首先怀疑了是不是板子上的纽扣电子是不是没电了导致外部晶振没有正常起振,用电压表进行了测量发现有电、之后将PLLM改回8,重新烧录程序后led灯正常闪烁,初步判断,可能是设置的频率不对。
③代码中PLLN以及后面的设置与参考资料中的代码均不同,经查证,target_freq_mhz在系统初始化后会被赋值为168,刚好是探索者的系统时钟频率,168Mhz,因此,后面的代码部分不需要进行修改。
2. 获取温湿度传感器数据
参考资料中用的是(I2C设备驱动+SHT3x软件包)
博主手头上只有DHT11,一样可以测试温湿度数据,只不过DHT11采取单总线的方式依赖时序进行通信,并非I2C,不过一样可以做。
2.1 添加DHT11软件包
双击RT-Thread Settings,在软件包中心点立即添加,在弹出来的界面中输入DHT11,如下图所示:
双击即可查看概要,打开后发现并没有特别重要的内容,即使没有查看概要,直接点了添加,ctrl+s保存之后,也可以在左侧工程目录中找到README.md文件,打开后发现与概要说明内容一样。
2.2 “个性化”修改
双击打开上图中的dht11_sample.c文件,可以在该文件中找到很明显的两行代码,如下图所示:
DHT11为单总线通信,三个引脚,一个VCC,一个GND,中间那个就是数据传输用的,因此,最关心的也是这个引脚,我用的板子是正点原子探索者STM32F407ZGT6,查阅电路原理图后发现,开发板上预留了DHT11/DS18B20的快捷接口,但因为口太小,DHT11无法直接插进去,因此引了三根线,如下图所示:绿色为VCC,橙色为数据线,蓝色为GND,查阅电路原理图得知数据线接的是PG9,因此将代码中的PIN脚改为PG9。
重新构建后发现报错,原因为找不到头文件“drv_gpio.h”,在左侧工程目录中找到drv_gpio.c文件,怎么会没有对应的头文件呢?打开后发现,确实没有,他用的是"board.h"、“drv_common.h”,在DHT11的源代码中进行修改,如下图:
2.3 运行结果
重新构建后可以烧录程序,烧录程序后打开控制台,可以看到DHT11初始化成功,温湿度数据正常输出,但是此时的温湿度数据并不是在main函数中输出的,因为我根本没改main函数,效果如下图:
3. 获取NTP时间
3.1 添加ESP8266设备驱动
3.1.1 使能libc组件
使用at_device软件包之前,需要先开启libc组件:
3.1.2 添加at_device软件包
项目中使用的是ESP8266设备,其基于AT框架的驱动示例代码在at_device软件包中提供,在RT-Thread软件包中心搜索at_device后添加,之后在at_device右键选择详细配置进入软件包配置页面,勾选乐鑫 ESP8266,修改如下:
注意:
①Wifi SSID和Wifi 密码设置为真实的可以连上网的wifi。
②修改AT 客户端设备名称
按照上述要求,需要在board.h文件中进行设备uart2的开启(这里为什么用uart2请往后看)
上述step1 翻译过来就是:根据串口号定义宏定义打开串口相关的内容,比如:……这里的示例代码中居然写成了UATR1,下面写的却是UART1,迷惑行为。
step2为定义对应的引脚,step3和step4是和DMA相关的,项目中没用到,所以不需要定义。
备注:参考资料2中命名为lpuart1,他也成功了,我一开始用的esp8266,报错,后来换成他用的lpuart1也报错,最后换成实例代码中的uart1,发现程序中uart1已经被用来与控制台交互了,因此换成了uart2,好了。
修改如下图所示,在原来UART1的基础上直接“照抄”了一下:
重新构建项目后运行,控制台输出下列字样:
备注:
①如果出现了提示RT_SERIAL_RB_BUFSZ的值不够用的警告,可以参考资料2中进行缓冲区大小的调整,我是直接调整后运行的,所以没有出现这种警告。
②在调试过程中,我出现了另一个错误(警告),如下图所示:
大概意思就是我的esp8266固件版本低了,需要升级才能支持AT+CIPDNS_CUR?指令,于是我把我的esp8266模块拆下来单独测试,它长这样:
用USB-TTL模块对应引脚相连,用AT指令测试了一下,内置的固件版本是1.5.4的,去安信可官网查了一下,最新的居然也是1.5.4的不过后面多了个1.5.4.1,是的,就多一个“.1”,本来不想整的,结果RT-Thread Studio报红出来让我更新,不更新看着难受,结果我就去了,结果……我就被坑了。
记不得在哪里下载到了v3.8.8的flash_download_tool固件烧写工具,然后下载了安信可官网1.5.4.1的最新固件包,然后打开烧写软件:
这个图上你们所看到的每一个勾选项我都查网上的资料或者通过AT指令查过了,不管怎么试,最后点击START都会卡在等待上电的界面,固件就是刷不进去,网上有人说8266模组要把IO_0接地,要在出现等待上电界面的时候将原本悬空的RST先接地再松开,让8266重启,要注意共地问题等,我全都试了,全都……不行。这个问题只能先放着,已经浪费了我很长时间了。
3.1.3 测试网络
1.ifconfig //使用该命令查看当前网卡配置
2.ping www.baidu.com //使用该命令测试外网是否可以ping通
ifconfig指令:
ping指令:
这里从测试结果中可以看到,www.baidu.com就是不行,用百度的ip地址就可以了,暂时是无法理解的,一度让我以为网络ping失败了,又让我联想到之前报的那个固件版本错误,可能是那个问题导致的。
3.2 添加NTP对时功能
3.2.1 添加netutils工具软件包
同样,在RT-Thread Setting软件中心搜索并添加“netutils”包,右键选择详细配置,如下图:
这里的网址并不需要修改。
3.2.2 开启软件模拟RTC
因为NTP工具在获取到网络时间后,需要同步到本地RTC,所以需要开启本地模拟RTC功能:
点亮之后,ctrl+s即自动导入工程。
3.2.3 测试NTP工作是否正常
使用NTP工具包自带的命令进行测试:
ntp_sync //获取NTP时间并同步到本地
可以从终端观察到有时间日期等信息返回。
3.2.4 编写上电自动同步时间代码
① 检测当前网络是否正常
在本项目中,需要上电连接网络之后,自动获取NTP时间同步到本地,供后续显示使用,这段代码放在main函数中执行。
netdev(network interface device),即网络接口设备,又称网卡。每一个用于网络连接的设备都可以注册成网卡,为了适配更多的种类的网卡,避免系统中对单一网卡的依赖,RT-Thread 系统提供了 netdev 组件用于网卡管理和控制。
使用 netdev 网卡功能相关操作函数,需要包含如下头文件:
#include <arpa/inet.h> /* 包含 ip_addr_t 等地址相关的头文件 */
#include <netdev.h> /* 包含全部的 netdev 相关操作接口函数 */
首先获取要操作的网卡对象,每个网卡中有唯一的网卡名称,可以通过网卡名称获取网卡对象:
struct netdev *netdev_get_by_name(const char *name);
netdev 网卡提供了一个宏定义用于判断网卡是否为 internet_up 状态,如下:(注意此行代码并不需要添加到main函数中,只是参考资料2的博主讲解(秀)给你看的)
#define netdev_is_internet_up(netdev)
通过这个宏定义即可判断当前网络状态是否正常,在main函数中添加如下代码:
//获取网卡对象
struct netdev* net = netdev_get_by_name("esp0");
//阻塞判断当前网络是否正常连接
while(netdev_is_internet_up(net) != 1)
{
rt_thread_mdelay(200);
}
//提示当前网络已就绪
rt_kprintf("network is ok!\n");
构建项目后下载,效果如下:
输出了一个network is ok!
② 网络正常后,获取NTP时间并同步到本地RTC
要使用NTP工具提供的API,首先包含头文件:
#include <ntp.h>
接着调用获取时间并同步的API,原型如下:(这行代码也不需要写到main函数里)
time_t ntp_sync_to_rtc(void);
在上面的代码之后继续添加NTP对时的代码:
//NTP自动对时
time_t cur_time;
cur_time = ntp_sync_to_rtc(NULL);
if (cur_time)
{
rt_kprintf("Cur Time: %s", ctime((const time_t*) &cur_time));
}
else
{
rt_kprintf("NTP sync fail.\n");
}
构建项目后下载,输出了两次当前时间,效果如下:
备注:
①感觉间隔挺长的,运行起来有点慢,等待后期学习再对此类问题优化。
4. OLED显示时钟和温湿度
4.1 开启C++组件支持
使用U8G2软件包需要C++组件支持,在RT-Thread项目设置中开启C++组件,如图:
开启之后保存设置,软件会自动添加C++组件到工程中,编译没有问题:
4.2 添加u8g2软件包并测试
c++组件测试编译没有问题之后,打开工程设置,搜索u8g2,添加软件包,添加之后右键详细设置,如下图,需使能基本示例:
ctrl+s保存配置,等系统重构工程之后双击打开在左侧工程目录中packages—>u8g2—>examples—>ssd1306……的.cpp文件,如下图:
需要根据开发板及个人喜好,修改i2c通信的端口:
这里我打开就是这样,所以oled屏直接SCL接PB6,SDA接PB7,但是由于之前我温湿度传感器没有用SHA3X,导致项目中还没添加i2c组件,在设置中点选i2c,如下图:
参考资料3中具体介绍了i2c怎么配置,其中还涉及到一个添加软件 I2C 源码的过程,我当时直接ctrl+s保存等待系统自动重构之后才想起来,在重新添加的时候发现工程中已经有了源码的两个文件,不过我还是选择了重新覆盖。
构建项目后下载,运行后可以看到oled被点亮(但是说实话特别糊,只能隐隐约约看到RT Thread字样)
4.3 编写OLED显示线程
在application分组下创建一个用户文件oled_display.cpp文件,存放本项目中的OLED显示代码。
参考资料4中有配合了SHA3X的源代码,能不能成功运行我不知道,由于我是采用的DHT11传感器,因此,对该代码进行了部分修改:
#include <rthw.h>
#include <rtthread.h>
#include <rtdevice.h>
#include <U8g2lib.h>
#include <stdio.h>
#include <drv_soft_i2c.h>
#include "board.h"
#include "drv_common.h"
#include "sensor.h"
//extern "C"
//{
//#include <sht3x.h>
//}
//extern "C"
//{
//sht3x_device_t sht3x_init(const char *i2c_bus_name, rt_uint8_t sht3x_addr);
//rt_err_t sht3x_read_singleshot(sht3x_device_t dev);
//}
#define OLED_I2C_PIN_SCL 22 // PB6
#define OLED_I2C_PIN_SDA 23 // PB7
static U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0,\
/* clock=*/ OLED_I2C_PIN_SCL,\
/* data=*/ OLED_I2C_PIN_SDA,\
/* reset=*/ U8X8_PIN_NONE);
#define SUN 0
#define SUN_CLOUD 1
#define CLOUD 2
#define RAIN 3
#define THUNDER 4
static void drawWeatherSymbol(u8g2_uint_t x, u8g2_uint_t y, uint8_t symbol)
{
// fonts used:
// u8g2_font_open_iconic_embedded_6x_t
// u8g2_font_open_iconic_weather_6x_t
// encoding values, see: https://github.com/olikraus/u8g2/wiki/fntgrpiconic
switch(symbol)
{
case SUN:
u8g2.setFont(u8g2_font_open_iconic_weather_6x_t);
u8g2.drawGlyph(x, y, 69);
break;
case SUN_CLOUD:
u8g2.setFont(u8g2_font_open_iconic_weather_6x_t);
u8g2.drawGlyph(x, y, 65);
break;
case CLOUD:
u8g2.setFont(u8g2_font_open_iconic_weather_6x_t);
u8g2.drawGlyph(x, y, 64);
break;
case RAIN:
u8g2.setFont(u8g2_font_open_iconic_weather_6x_t);
u8g2.drawGlyph(x, y, 67);
break;
case THUNDER:
u8g2.setFont(u8g2_font_open_iconic_embedded_6x_t);
u8g2.drawGlyph(x, y, 67);
break;
}
}
static void drawWeather(uint8_t symbol, int degree)
{
drawWeatherSymbol(0, 63, symbol);
u8g2.setFont(u8g2_font_logisoso32_tf);
u8g2.setCursor(55, 63);
u8g2.print(degree);
u8g2.print("C");
}
static void drawHumidity(uint8_t symbol, int humidity)
{
drawWeatherSymbol(0, 63, symbol);
u8g2.setFont(u8g2_font_logisoso32_tf);
u8g2.setCursor(55, 63);
u8g2.print(humidity);
u8g2.print("%");
}
void oled_display()
{
u8g2.begin();
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_logisoso32_tf);
u8g2.setCursor(48+3, 42);
u8g2.print("Hi~"); // requires enableUTF8Print()
u8g2.setFont(u8g2_font_6x13_tr); // choose a suitable font
u8g2.drawStr(30, 60, "By Mculover666"); // write something to the internal memory
u8g2.sendBuffer();
// sht3x_device_t sht3x_device;
// sht3x_device = sht3x_init("i2c1", 0x44);
rt_thread_mdelay(2000);
int status = 0;
char mstr[3];
char hstr[3];
time_t now;
struct tm *p;
int min = 0, hour = 0;
int temperature = 0,humidity = 0;
struct rt_sensor_data sensor_data;
while(1)
{
switch(status)
{
case 0:
now = time(RT_NULL);
p=gmtime((const time_t*) &now);
hour = p->tm_hour;
min = p->tm_min;
sprintf(mstr, "%02d", min);
sprintf(hstr, "%02d", hour);
u8g2.firstPage();
do {
u8g2.setFont(u8g2_font_logisoso42_tn);
u8g2.drawStr(0,63,hstr);
u8g2.drawStr(50,63,":");
u8g2.drawStr(67,63,mstr);
} while ( u8g2.nextPage() );
rt_thread_mdelay(5000);
status = 1;
break;
case 1:
if(rt_device_read(rt_device_find("temp_dht11"), 0, &sensor_data, 1) == 1)
{
if(sensor_data.data.temp >= 0){
temperature = (sensor_data.data.temp & 0xffff) >> 0; // get temp
}
}
else
{
temperature = 0;
}
u8g2.clearBuffer();
drawWeather(SUN, temperature);
u8g2.sendBuffer();
rt_thread_mdelay(5000);
status = 2;
break;
case 2:
if(rt_device_read(rt_device_find("temp_dht11"), 0, &sensor_data, 1) == 1)
{
if(sensor_data.data.temp >= 0){
humidity = (sensor_data.data.temp & 0xffff0000) >> 16;
}
}
else
{
humidity = 0;
}
u8g2.clearBuffer();
drawHumidity(RAIN, humidity);
u8g2.sendBuffer();
rt_thread_mdelay(5000);
status = 0;
break;
}
}
}
MSH_CMD_EXPORT(oled_display, oled start);
运行结果如下所示(循环刷新数据):
参考资料
1.https://blog.csdn.net/Mculover666/article/details/104146623
2.https://mculover666.blog.csdn.net/article/details/104418075
3.https://mculover666.blog.csdn.net/article/details/104153715
4.https://mculover666.blog.csdn.net/article/details/104422501