- 2024-12-17
-
上传了资料:
带健康提醒的86盒桌面助手代码
- 2024-10-31
-
发表了主题帖:
【2024 DigiKey 创意大赛】带健康提醒的86盒桌面助手
**一、作品简介**
设计名称:带健康提醒的86盒桌面助手
作者:pomin
项目用到的板卡:使用 ESP32-S3-LCD-Ev-Board开发板,采用的是ESP32-S3这款 MCU,板卡板载了4寸的电容触摸屏,功能强悍,做HMI应用十分合适。
作品功能介绍:借此次得捷大赛的机会,制作了一款带健康提醒的86盒桌面助手,86盒可以摆在桌子上,实时地检测前方的温度,也就是监测使用者的体表温度,并显示在屏幕上面,并可通过网络来获取时间、天气等显示在屏幕上,给使用者提供出行建议等,软件采用LVGL开源GUI界面库,使使用者更加感到可视化、智能化设备带来的便捷。
**二、系统框图**
本项目使用ESP32-S3-LCD-Ev-Board开发板来制作,用LVGL完成了十分美观的界面的绘制,采用欧姆龙D6T-01a传感器来监测体表温度,接入到 HomeAssistant 家庭自动化,搭配 Node-RED 来实现温度监控上传到HomeAssistant,系统框图如下图所示。
**三、各部分功能说明**
ESP32-S3-LCD-Ev-Board板卡支持外接的排针接口,所以将欧姆龙的D6T温度传感器连接到开发板,使用I2C接口和板卡通讯,采用MQTT协议与服务器来通讯。
使用ESP-IDF进行开发,在开发板上电的时候自动联网、连接家庭 MQTT 服务器,并订阅指定主题,定时上传D6T采集数据
使用ESP32-S3-LCD-Ev-Board 板卡板载的4寸电容触摸屏来实现HMI,使用LVGL完成了十分美观的界面的绘制,实时的监控当前使用者的体温,并且接入到HomeAssistant。
使用 Node-RED,将开发板上传的温度数据通过Javascript脚本解析然后映射到HomeAssistant。
**六、项目总结**
总结:用LVGL搭配GUI Guider完成了界面的绘制,采用 MQTT 协议与家庭服务器来通讯,通过 MQTT 接入到 HomeAssistant 家庭自动化,搭配 Node-RED 来实现家庭自动化流的创建,操控家中各种智能设备。
帖子分享链接汇总:
[【2024 DigiKey 创意大赛】开箱贴(ESP32-S3-LCD、D6T传感器)](https://bbs.eeworld.com.cn/thread-1289930-1-1.html)
[【2024 DigiKey 创意大赛】D6T非接触温度传感器调试](https://bbs.eeworld.com.cn/thread-1291214-1-1.html)
[【2024 DigiKey 创意大赛】搭建环境、运行86盒demo](https://bbs.eeworld.com.cn/thread-1295847-1-1.html)
[【2024 DigiKey 创意大赛】开发板读取D6T传感器值、LVGL显示](https://bbs.eeworld.com.cn/thread-1297965-1-1.html)
[localvideo]b54c0d08f5e0d29a2d6894de774dd88f[/localvideo]
源码:https://download.eeworld.com.cn/detail/pomin/635329
-
发表了主题帖:
【2024 DigiKey 创意大赛】开发板读取D6T传感器值、LVGL显示
为了把 D6T 传感器连接到开发板,先查看原理图,开发板有一些预留的 IO 接口,但是大多数都不能随意使用
然后查看原理图可以知道 IO47 和 IO48 这两个引脚是作为了 IIC 使用,外接的是屏幕板的电容触摸芯片 FT5406,在代码中也可以看到:
然后查了查文档,FT5406 的 IIC 七位地址是 0x38,而 D6T 的 IIC 七位地址是 0x0A,所以可以把这两个器件都接在 IO47、IO48,然后把 D6T 模块接在开发板的 IO47、IO48上面,代码如下
```c
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include "core/lv_disp.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_check.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "bsp_board_extra.h"
#include "bsp/esp-bsp.h"
#include
#include
#include "driver/i2c.h"
static char *TAG = "app_main";
#define LOG_MEM_INFO (0)
/* defines */
#define D6T_ADDR 0x0A // for I2C 7bit address
#define D6T_CMD 0x4C // for D6T-44L-06/06H, D6T-8L-09/09H, for D6T-1A-01/02
#define N_ROW 1
#define N_PIXEL 1
#define N_READ ((N_PIXEL + 1) * 2 + 1)
uint8_t rbuf[N_READ];
double ptat;
double pix_data[N_PIXEL];
uint8_t calc_crc(uint8_t data) {
int index;
uint8_t temp;
for (index = 0; index < 8; index++) {
temp = data;
data convert a 16bit data from the byte stream.
*/
int16_t conv8us_s16_le(uint8_t* buf, int n) {
uint16_t ret;
ret = (uint16_t)buf[n];
ret += ((uint16_t)buf[n + 1])
-
加入了学习《【2024DigiKey创意大赛】基于AIOT的智能家居设备开发演示视频》,观看 【2024DigiKey创意大赛】基于AIOT的智能家居设备开发
- 2024-10-30
-
加入了学习《ESP32-S3-LVGL》,观看 自行车智能灯
- 2024-10-29
-
加入了学习《 【2024 DigiKey创意大赛】 《智能起居室环境控制台》任务报告汇总》,观看 【2024 DigiKey创意大赛】 《智能起居室环境控制台》任务报告汇总
- 2024-10-17
-
加入了学习《Followme-3 XIAO开发板作业视频》,观看 Followme3-作业提交
- 2024-10-11
-
发表了主题帖:
【2024 DigiKey 创意大赛】搭建环境、运行86盒demo
# 搭建环境
Linux下配置 ESP-idf 还是很轻松的,先 clone 并安装一下,我这里用的是 Ubuntu 18.04 的虚拟机
先装点软件包
```sh
sudo apt-get install git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0
```
然后 clone 并运行安装程序
```bash
git clone -b v5.1 --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
./install.sh
. ./export.sh
```
此时工具链就安装完成了,可以看到比 Windows 要方便许多
试一下编译 `cd examples/get-started/blink/ && idf.py build`
编译成功
为了方便,再添加一个快捷命令,配置脚本到 bash 或者 zsh ( 我这里用的是 zsh )
```bash
#zsh
nano ~/.zshrc
#bash
nano ~/.bashrc
```
添加重定向脚本
```bash
alias idf='idf.py'
alias iidf='. ~/esp/esp-idf/export.sh'
export IDF_TOOLS_PATH=~/.espressif
export idf
export iidf
```
更新 bash 或 zsh 配置
```bash
#zsh
source ~/.zshrc
#bash
source ~/.bashrc
```
然后要在当前终端初始化的话就 `iidf`、idf 重定向到 idf.py,编译直接 `idf build` 就行
# 运行 86 盒 demo
先把这个开发板的官方代码仓库克隆下来,官方的仓库包含有很很多开发板的 demo 程序,这里只留 esp32-s3-lcd-ev-board 这个开发板的 demo 程序
```sh
git clone https://github.com/espressif/esp-dev-kits.git
cd esp-dev-kits
mv esp32-s3-lcd-ev-board ..
rm -rf esp-dev-kits
cd esp32-s3-lcd-ev-board
```
可以看到目录中有许多的 demo 程序,这里编译 86box_demo 这个 demo
```sh
cd examples/86box_demo
idf build
```
然后烧录并打开串口监视
```sh
idf flash -p /dev/ttyUSB0 -b 921600 && idf monitor -p /dev/ttyUSB0 -b 115200
```
同时可以看到板子上的屏幕已经运行 86 盒的 demo 了
- 2024-08-22
-
发表了主题帖:
【2024 DigiKey 创意大赛】D6T非接触温度传感器调试
本帖最后由 pomin 于 2024-8-22 00:28 编辑
这次活动选的传感器是欧姆龙的 D6T-1A-02,下载了规格书看到是 I2C 接口通讯
接口座子的型号是 GH1.25 4P 带锁扣,开发板的排针都是 2.54 间距的,所以还需要整一根 GH1.25 转 2.54 间距的转接线
打开 D6T 的 user manual 可以看到示例电路图,这模块居然没有板载 I2C 的上拉电阻??
也可以看到 D6T-1A-02 的 I2C 协议数据包,从机地址是 0x0A,时钟速度 100kHz,写一个 0x4C 的命令后读取 5 个字节的数据
然后查到官方提供的有 arduino 的驱动代码, [https://github.com/omron-devhub/d6t-2jcieev01-arduino/blob/master/examples/d6t-1a/d6t-1a.ino](https://github.com/omron-devhub/d6t-2jcieev01-arduino/blob/master/examples/d6t-1a/d6t-1a.ino)
然后用 USB 转 I2C 调试器连接到 D6T 传感器,同时在板子底部的 I2C 接口飞两个4.7k的上拉电阻,复制官方的arduino代码然后修改一下,使用 I2C 协议读取到模块的温度采集数据。
[localvideo]135775d94635f981c51a88baf858526f[/localvideo]
- 2024-08-08
-
回复了主题帖:
【2024 DigiKey 创意大赛】开箱贴(ESP32-S3-LCD、D6T传感器)
wangerxian 发表于 2024-8-8 13:53
欧姆龙的D6T传感器是做什么检测的?
非接触式温度传感器
-
发表了主题帖:
【2024 DigiKey 创意大赛】开箱贴(ESP32-S3-LCD、D6T传感器)
这次报名参加了今年的得捷创意大赛,很荣幸入选了,在得捷商城下单了一个ESP32-S3-LCD和欧姆龙的D6T传感器,没想到这次得捷的物流如此迅速,收到货还是十分的激动的,开始拆箱。
严严实实的包装盒
ESP32-S3-LCD开发板有着非常精致的包装盒,D6T是欧姆龙的非接触式温度传感器
拆开盒子,ESP32-S3-LCD这开发板还蛮大的
插上电,就是官方的一个运行LVGL界面库的86界面
- 2024-05-17
-
发表了主题帖:
【2023 DigiKey大赛参与奖】开箱帖 Raspberry Pi 5 4G
本帖最后由 pomin 于 2024-5-17 02:12 编辑
> 这次参加了2023得捷大赛,虽然没有获得大奖,最后也有了一个参与奖,很开心。下单了一个4GB内存的树莓派5开发板,快递今天到了,板子很不错。
快递袋
树莓派5的盒子,还是这一贯的玫红色包装盒
打开盒子就是树莓派5了,这代虽然没有之前的好看些,不过性能确实提高了不少
感谢EEWorld和得捷举办的本次活动。
希望EEWorld和得捷越办越好!!!
- 2024-04-24
-
加入了学习《【DigiKey创意大赛】多通道微型气相色谱采集单元》,观看 多通道微型气相色谱采集单元
- 2024-04-15
-
回复了主题帖:
【STM32F411Nucleo测评】驱动 1.3 寸 LCD 屏幕
deng0713 发表于 2024-4-14 22:26
请问这个扩展版是嘉立创元件库里的吗还是大佬自己画的(☆▽☆)
自己画的
- 2024-03-11
-
回复了主题帖:
【STM32F411Nucleo测评】移植 Freemodbus 库
qiao--- 发表于 2024-3-10 00:07
请问一下,你是用的这个modbus调试工具是什么啊
mthings
- 2024-03-08
-
发表了主题帖:
【STM32F411Nucleo测评】modbus&LVGL 屏显从站
> 在电机应用中,尝尝需要获知电机的运行参数,例如转速、温度等,许多驱动板也会带有屏显,或者通过modbus连接到上位机或者组态屏来监控电机状态。本文使用 LVGL 和 Freemodbus 库制作了一个带屏线从站的 demo
## 往期链接
[【STM32F411Nucleo测评】开箱,搭建开发环境,串口输出](https://bbs.eeworld.com.cn/thread-1272849-1-1.html)
[【STM32F411Nucleo测评】驱动 1.3 寸 LCD 屏幕](https://bbs.eeworld.com.cn/thread-1272852-1-1.html)
[【STM32F411Nucleo测评】移植多功能按键驱动库](https://bbs.eeworld.com.cn/thread-1273453-1-1.html)
[【STM32F411Nucleo测评】移植 Freemodbus 库](https://bbs.eeworld.com.cn/thread-1273454-1-1.html)
[【STM32F411Nucleo测评】移植 LVGL 界面库](https://bbs.eeworld.com.cn/thread-1273455-1-1.html)
在前文的介绍中,已经完成了对 LVGL 和 Freemodbus 的移植,下面介绍做一个上层应用——modbus LVGL 屏显从站的 demo 制作。
## GUI Guider 设计界面
GUI Guider 是 NXP 给 LVGL 开发的可视化界面编辑、模拟器,所见即所得,同时也可以在此之上完成对于时间、定时器、界面切换任务的添加,这里绘制了一个 demo 界面,有两个按键、一个滑条和一个标签文本。
设计的操作逻辑如下:
- 按下 Down 按键减小速度、Up 按键增大速度
- 滑条可以百分比放缩显示当前速度
- 标签文本显示当前速度
对于上述的功能写代码当然是比较容易实现的,但是这里是在 GUI Guider 中添加这些事件和任务的响应代码。
按下 Down 按键减小速度、Up 按键增大速度时对应的是 Click 事件,这里添加 Click 的事件代码,当 Down 按下后会设置标签文本并且修改滑条的位置
```c
unsigned int speed = 1000;
```
```c
speed-=10;
lv_label_set_text_fmt(guider_ui.screen_label_1, "Speed: %dRPM", speed);
lv_slider_set_value(guider_ui.screen_slider_1, speed / 16, LV_ANIM_ON);
```
类似的, Up 按键的 Click 事件添加代码
```c
speed+=10;
lv_label_set_text_fmt(guider_ui.screen_label_1, "Speed: %dRPM", speed);
lv_slider_set_value(guider_ui.screen_slider_1, speed / 16, LV_ANIM_ON);
```
然后在 GUI Guider 中仿真一下,与预期的想法一致,然后生成代码加入到 keil 工程中
添加的文件如下,当然用别的字体的话字体文件跟这个肯定名字不一样
然后就可以把生成的界面代码加载起来了,先在 main.c 中定义一个全局变量
```c
lv_ui guider_ui;
```
然后在 while(1) 之前加载 GUI Guider 绘制的界面
```c
setup_ui(&guider_ui);
```
编译烧录到开发板
此时,这个按键是没法操作的,因为在仿真器中加入的是 Windows 的输入设备,在 MCU 端还需要再把按键注册到 LVGL 的输入设备中。
编写 lv_port_indev.c 的代码如下,主要就是在 button_is_pressed 函数中获取按键的电平状态,在初始化时定义按键到 btn_points,按键按下时相当于对应的坐标点被点击。
```c
/*Copy this file as "lv_port_indev.c" and set this value to "1" to enable content*/
#if 1
/*********************
* INCLUDES
*********************/
#include "lv_port_indev.h"
#include "lvgl/lvgl.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static void button_init(void);
static void button_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
static int8_t button_get_pressed_id(void);
static bool button_is_pressed(uint8_t id);
/**********************
* STATIC VARIABLES
**********************/
lv_indev_t * indev_button;
static int32_t encoder_diff;
static lv_indev_state_t encoder_state;
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
void lv_port_indev_init(void)
{
/**
* Here you will find example implementation of input devices supported by LittelvGL:
* - Touchpad
* - Mouse (with cursor support)
* - Keypad (supports GUI usage only with key)
* - Encoder (supports GUI usage only with: left, right, push)
* - Button (external buttons to press points on the screen)
*
* The `..._read()` function are only examples.
* You should shape them according to your hardware
*/
static lv_indev_drv_t indev_drv;
/*------------------
* Button
* -----------------*/
/*Initialize your button if you have*/
button_init();
/*Register a button input device*/
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_BUTTON;
indev_drv.read_cb = button_read;
indev_button = lv_indev_drv_register(&indev_drv);
/*Assign buttons to points on the screen*/
static const lv_point_t btn_points[2] = {
{60, 60}, /*Button 0 -> x:10; y:10*/
{180, 60}, /*Button 1 -> x:40; y:100*/
};
lv_indev_set_button_points(indev_button, btn_points);
}
/*------------------
* Button
* -----------------*/
/*Initialize your buttons*/
static void button_init(void)
{
/*Your code comes here*/
}
/*Will be called by the library to read the button*/
static void button_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
static uint8_t last_btn = 0;
/*Get the pressed button's ID*/
int8_t btn_act = button_get_pressed_id();
if(btn_act >= 0) {
data->state = LV_INDEV_STATE_PR;
last_btn = btn_act;
}
else {
data->state = LV_INDEV_STATE_REL;
}
/*Save the last pressed button's ID*/
data->btn_id = last_btn;
}
/*Get ID (0, 1, 2 ..) of the pressed button*/
static int8_t button_get_pressed_id(void)
{
uint8_t i;
/*Check to buttons see which is being pressed (assume there are 2 buttons)*/
for(i = 0; i < 2; i++) {
/*Return the pressed button's ID*/
if(button_is_pressed(i)) {
return i;
}
}
/*No button pressed*/
return -1;
}
#include "main.h"
/*Test if `id` button is pressed or not*/
static bool button_is_pressed(uint8_t id)
{
/*Your code comes here*/
if (id) return !HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin);
else return !HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin);
return false;
}
#else /*Enable this file at the top*/
/*This dummy typedef exists purely to silence -Wpedantic.*/
typedef int keep_pedantic_happy;
#endif
```
然后在 main.c 的初始化代码中加入 indev 的初始化
```c
lv_init();
lv_port_disp_init();
lv_port_indev_init();
setup_ui(&guider_ui);
```
编译烧录到开发板,可以看到操作按键修改速度值,modbus主机端也可以实时的监控到这个值
[localvideo]1dbe188ba2d29ca0d4708a1f75745f19[/localvideo]
- 2024-03-05
-
回复了主题帖:
【STM32F411Nucleo测评】移植 LVGL 界面库
LitchiCheng 发表于 2024-3-5 09:04
刷新率怎么样,spi通信加上DMA大概可以到多少fps
有动画的时候FPS也可以稳定60,很丝滑
-
加入了学习《follow me 四期项目提交视频》,观看 follow me 四期项目提交视频
- 2024-03-04
-
发表了主题帖:
【STM32F411Nucleo测评】移植 LVGL 界面库
> STM32F411RE 有 512KB 的 Flash 和 128KB 的 RAM,资源丰富,本文介绍如何移植 LVGL 界面库到 Nucleo-F411 开发板上面
## 资料获取
LVGL 是比较知名的嵌入式 GUI 库,代码在 GitHub 可以获得,地址 https://github.com/lvgl/lvgl ,这里移植 V8.2 版本,在拉取时使用如下命令,不直接拉取最新版,因为最新版是9.0版本的
```bash
git clone -b release/v8.2 https://github.com/lvgl/lvgl.git
```
然后将 LVGL 除了别的平台的代码外全部加入 keil 工程中
## 显示接口适配
LVGL的显示接口主要就是一个刷屏函数disp_flush,这里在之前文章的 LCD 完成刷屏的基础上,修改思路如下:
删除全屏缓存区,因为占用过多 RAM,改为选择显示空间后使用DMA传输LVGL的color参数
LVGL 的 disp_flush 接口代码如下
```c
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
/*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/
LCD_FillWindow(area->x1,area->y1,area->x2,area->y2, (uint16_t*)color_p);
/*IMPORTANT!!!
*Inform the graphics library that you are ready with the flushing*/
lv_disp_flush_ready(disp_drv);
}
void LCD_FillWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t *pixels)
{
u16 i,j,width,height;
width = x1 - x0 + 1;
height = y1 - y0 + 1;
uint32_t size = width * height;
LCD_SetFrame(x0, y0, x1, y1);
hspi1.Init.DataSize = SPI_DATASIZE_16BIT;
hspi1.Instance->CR1|=SPI_CR1_DFF;
HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, 1);
HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, 0);
HAL_SPI_Transmit_DMA(&hspi1,(uint8_t*)pixels, size);
while(__HAL_DMA_GET_COUNTER(&hdma_spi1_tx)!=0);
HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, 1);
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Instance->CR1&=~SPI_CR1_DFF;
}
```
至此 LVGL 的刷屏接口就适配好了,其中为了加快执行的速度,部分代码直接操作寄存器来完成
## 时钟接口适配
在 CubeMX 中初始化 TIM10 配置为1ms周期中断
在中断函数中调用 lv_tick_inc 函数,为LVGL提供时钟
```c
void TIM1_UP_TIM10_IRQHandler(void)
{
/* USER CODE BEGIN TIM1_UP_TIM10_IRQn 0 */
/* USER CODE END TIM1_UP_TIM10_IRQn 0 */
HAL_TIM_IRQHandler(&htim10);
/* USER CODE BEGIN TIM1_UP_TIM10_IRQn 1 */
lv_tick_inc(1);
/* USER CODE END TIM1_UP_TIM10_IRQn 1 */
}
s
```
## 主函数代码
最后主函数中调用 lv_init、lv_port_disp_init 和 lv_task_handler,即可完成 LVGL 的移植
```c
lv_init();
lv_port_disp_init();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
lv_task_handler();
}
/* USER CODE END 3 */
```
单单有 LVGL 初始化不够的,还需要有一个界面来交给它来绘制,下面写段验证代码
## 验证
代码如下,创建一个自定义风格的滑动条控件,百分比为 70%,长度为200,居中显示
```c
void foo(void)
{
static lv_style_t style_indic;
lv_style_init(&style_indic);
lv_style_set_bg_color(&style_indic, lv_palette_lighten(LV_PALETTE_RED, 3));
lv_style_set_bg_grad_color(&style_indic, lv_palette_main(LV_PALETTE_RED));
lv_style_set_bg_grad_dir(&style_indic, LV_GRAD_DIR_HOR);
static lv_style_t style_indic_pr;
lv_style_init(&style_indic_pr);
lv_style_set_shadow_color(&style_indic_pr, lv_palette_main(LV_PALETTE_RED));
lv_style_set_shadow_width(&style_indic_pr, 10);
lv_style_set_shadow_spread(&style_indic_pr, 3);
/*Create an object with the new style_pr*/
lv_obj_t * obj = lv_slider_create(lv_scr_act());
lv_obj_add_style(obj, &style_indic, LV_PART_INDICATOR);
lv_obj_add_style(obj, &style_indic_pr, LV_PART_INDICATOR | LV_STATE_PRESSED);
lv_slider_set_value(obj, 70, LV_ANIM_OFF);
lv_obj_center(obj);
}
```
添加到主函数中。
```c
lv_init();
lv_port_disp_init();
foo();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
lv_task_handler();
}
/* USER CODE END 3 */
```
编译烧录到开发板中,可以看到显示出了使用LVGL绘制的滑动条
-
发表了主题帖:
【STM32F411Nucleo测评】移植 Freemodbus 库
> 本文介绍如何移植 FreeModbus 库,在 Nucleo-F411 板上实现 Modbus 通信
## 资料准备
FreeModbus 的代码是存放在 GitHub 的,地址 https://github.com/cwalter-at/freemodbus ,拉取到本地,其中关键的就是demo和modbus两个文件夹,demo存放一些移植好的工程,modbus中存放库代码。
## 硬件连接
查看原理图的串口是接在STLink 的虚拟串口上面的,管脚为PA2和PA3
## 接口配置
将拉取到的 FreeModbus 代码添加到 keil 工程中,其中需要添加库代码中的 functions、rtu 文件夹中的代码和 mb.c
此外还需要把demo中BARE文件夹的代码复制到工程中,这里同时将demo.c修改为了data.c添加到工程
添加的代码文件如下
需要的代码文件都添加好了,下面开始对 portxxx.c进行适配,其中 portserial.c 是串口的接口层, porttimer.c 是定时器的接口层。
配置 USART2,参数如下,设置波特率为115200bps
定时器TIM11周期设置为50us
同时将 USART2 和 TIM11 的中断使能
### 串口接口适配
利用 HAL_UART_TxCpltCallback,HAL_UART_RxCpltCallback 两个HAL库的两个回调函数编写如下代码
```c
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == uart->Instance)
{
pxMBFrameCBByteReceived();
HAL_UART_Receive_IT(uart, &singlechar, 1);
}
}
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == uart->Instance)
{
pxMBFrameCBTransmitterEmpty();
}
}
```
串口收发的接口代码如下
```c
/* ----------------------- Static variables ---------------------------------*/
UART_HandleTypeDef *uart = &huart2;
static uint8_t singlechar;
/* ----------------------- Start implementation -----------------------------*/
BOOL xMBPortSerialInit( UCHAR ucPort, ULONG ulBaudRate,
UCHAR ucDataBits, eMBParity eParity )
{
return TRUE;
}
void vMBPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable)
{
if(xRxEnable)
{
HAL_UART_Receive_IT(uart, &singlechar, 1);
}
else
{
HAL_UART_AbortReceive_IT(uart);
}
if(xTxEnable)
{
pxMBFrameCBTransmitterEmpty();
}
else
{
HAL_UART_AbortTransmit_IT(uart);
}
}
void vMBPortClose(void)
{
HAL_UART_AbortReceive_IT(uart);
HAL_UART_AbortTransmit_IT(uart);
}
BOOL xMBPortSerialPutByte(CHAR ucByte)
{
HAL_UART_Transmit_IT(uart, (uint8_t*)&ucByte, 1);
return TRUE;
}
BOOL xMBPortSerialPutBytes(volatile UCHAR *ucByte, USHORT usSize)
{
HAL_UART_Transmit_IT(uart, (uint8_t *)ucByte, usSize);
return TRUE;
}
BOOL xMBPortSerialGetByte(CHAR * pucByte)
{
*pucByte = (uint8_t)(singlechar);
return TRUE;
}
```
### 定时器接口适配
定时器使用 HAL_TIM_PeriodElapsedCallback 这个 HAL 提供的定时器周期中断时的回调函数
```c
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == tim->Instance)
{
if((++counter) >= timeout)
pxMBPortCBTimerExpired();
}
}
```
其他的代码就是使能、失能 TIM 的一些接口代码
```c
/* ----------------------- User defenitions ---------------------------------*/
TIM_HandleTypeDef *tim = &htim11;
static uint16_t timeout = 0;
volatile uint16_t counter = 0;
/* ----------------------- Start implementation -----------------------------*/
BOOL xMBPortTimersInit( USHORT usTim1Timerout50us )
{
timeout = usTim1Timerout50us;
return TRUE;
}
inline void vMBPortTimersEnable( )
{
counter=0;
HAL_TIM_Base_Start_IT(tim);
}
inline void vMBPortTimersDisable( )
{
HAL_TIM_Base_Stop_IT(tim);
}
inline void
vMBPortTimersDelay( USHORT usTimeOutMS )
{
HAL_Delay(usTimeOutMS);
}
```
至此接口层的代码就适配好了,代码量不大,还算比较容易,下面开始主函数编写应用代码来验证
## 验证
将main函数中while前后的代码改为如下代码,先初始化modbus库然后使能,在while循环中轮询。
```c
eMBInit(MB_RTU, 0x01, 0, 115200, MB_PAR_NONE); // 初始化modbus为RTU方式,波特率9600,奇校验
eMBEnable(); // 使能modbus协议栈
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
eMBPoll(); // 轮训查询
}
/* USER CODE END 3 */
```
光有这些还是不够的,现在已经完成了通信过程的适配,还需要提供保持寄存器、线圈等数据源代码,在data.c中编写代码如下
```c
#include "mb.h"
#include "mbport.h"
// 十路输入寄存器
#define REG_INPUT_SIZE 10
uint16_t REG_INPUT_BUF[REG_INPUT_SIZE];
// 十路保持寄存器
#define REG_HOLD_SIZE 10
uint16_t REG_HOLD_BUF[REG_HOLD_SIZE];
// 十路线圈
#define REG_COILS_SIZE 10
uint8_t REG_COILS_BUF[REG_COILS_SIZE] = {1, 1, 1, 1, 0, 0, 0, 0, 1, 1};
// 十路离散量
#define REG_DISC_SIZE 10
uint8_t REG_DISC_BUF[REG_DISC_SIZE] = {1,1,1,1,0,0,0,0,1,1};
/// CMD4命令处理回调函数
eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
USHORT usRegIndex = usAddress - 1;
// 非法检测
if((usRegIndex + usNRegs) > REG_INPUT_SIZE)
{
return MB_ENOREG;
}
// 循环读取
while( usNRegs > 0 )
{
*pucRegBuffer++ = ( unsigned char )( REG_INPUT_BUF[usRegIndex] >> 8 );
*pucRegBuffer++ = ( unsigned char )( REG_INPUT_BUF[usRegIndex] & 0xFF );
usRegIndex++;
usNRegs--;
}
// 模拟输入寄存器被改变
for(usRegIndex = 0; usRegIndex < REG_INPUT_SIZE; usRegIndex++)
{
REG_INPUT_BUF[usRegIndex]++;
}
return MB_ENOERR;
}
/// CMD6、3、16命令处理回调函数
eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
{
USHORT usRegIndex = usAddress - 1;
// 非法检测
if((usRegIndex + usNRegs) > REG_HOLD_SIZE)
{
return MB_ENOREG;
}
// 写寄存器
if(eMode == MB_REG_WRITE)
{
while( usNRegs > 0 )
{
REG_HOLD_BUF[usRegIndex] = (pucRegBuffer[0] 0 )
{
*pucRegBuffer++ = ( unsigned char )( REG_HOLD_BUF[usRegIndex] >> 8 );
*pucRegBuffer++ = ( unsigned char )( REG_HOLD_BUF[usRegIndex] & 0xFF );
usRegIndex++;
usNRegs--;
}
}
return MB_ENOERR;
}
/// CMD1、5、15命令处理回调函数
eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
{
USHORT usRegIndex = usAddress - 1;
UCHAR ucBits = 0;
UCHAR ucState = 0;
UCHAR ucLoops = 0;
// 非法检测
if((usRegIndex + usNCoils) > REG_COILS_SIZE)
{
return MB_ENOREG;
}
if(eMode == MB_REG_WRITE)
{
ucLoops = (usNCoils - 1) / 8 + 1;
while(ucLoops != 0)
{
ucState = *pucRegBuffer++;
ucBits = 0;
while(usNCoils != 0 && ucBits < 8)
{
REG_COILS_BUF[usRegIndex++] = (ucState >> ucBits) & 0X01;
usNCoils--;
ucBits++;
}
ucLoops--;
}
}
else
{
ucLoops = (usNCoils - 1) / 8 + 1;
while(ucLoops != 0)
{
ucState = 0;
ucBits = 0;
while(usNCoils != 0 && ucBits < 8)
{
if(REG_COILS_BUF[usRegIndex])
{
ucState |= (1 REG_DISC_SIZE)
{
return MB_ENOREG;
}
ucLoops = (usNDiscrete - 1) / 8 + 1;
while(ucLoops != 0)
{
ucState = 0;
ucBits = 0;
while(usNDiscrete != 0 && ucBits < 8)
{
if(REG_DISC_BUF[usRegIndex])
{
ucState |= (1