STM32与串口屏交互(USART HMI)
不管是备战电赛还是准备毕设,一块能与单片机交互的屏幕显得尤为重要,相较于传统的SPI,IIC通信的0.96寸OLED还是管脚较多的TFT屏幕,串口屏综合了以上屏幕的特点,即尺寸大、管脚少,能够充分减少占用单片机的I/O资源,且支持触摸。
串口屏可作为输出设备(显示)以及输入设备(按键),开发难度小,操作简单,软件要求低且拥有专门的上位机辅助开发。
1. USB TO TTL模块
串口屏顾名思义是通过串口通信的方式来实现数据的传输,所以我们要准备一个USB TO TTL模块。作用是用来上位机(电脑)与串口屏的通讯。
2.STM32单片机(以stm32f103c8t6为例)
3.HMI USART串口屏
本人使用使用的是陶晶驰的串口屏。型号为TJC4832T135_011

电阻式触摸屏比电容式体验效果稍差一点不过并无大碍,3.5寸的屏幕尺寸足够我们的项目需求。人机交互时,大小也正合适。串口屏共有4根线分别是5V、TX、RX、GND.当串口屏与上位机通讯时串口屏的RX接USB转TTL模块的TXD,串口屏的TX接串口模块的RX。当串口屏与单片机(stm32f103c8t6)通讯时,我们要软件配置使用哪个串口从而决定硬件连接。
若使用USART1
RX------------PA9
TX------------PA10
若使用USART2
RX------------PA2
TX------------PA3
若使用USART3
RX------------PB10
TX------------PB11
当我们在做小项目时,我们要根据项目的需求去设计对应的界面。以达到我们预期的效果。我们使用官方给出的上位机USART HMI软件去设计界面。
上位机下载:USART HMI 资料中心
(1)打开USART HMI 软件,点击新建
这里需要我们选择对应串口屏的型号,本人的是TJC4832T135_011。
显示方向我们一般默认90(横屏)。串口屏默认波特率为9600。如需要改变请查阅资料编写代码。
(2)从工具箱中拖需要使用的组件。比较常用就是文本、按钮,曲线等。去设计我们想要的界面例如背景色,控件的大小等等。
(3)点击软件左上方工具按钮,我们要提前导入字体也就是字库制作,我们选择我们想要的字体以及大小即可以生成字库。每生成一个字库并且添加到工程中都会有一个字体ID。
(4)当我们使用文本或者数字控件时,我们需要不同的字体或大小时,我们可以再右下角属性一栏中修改font参数,修改为你需要的字体ID即可。如下图所示:

(5)我们可以在软件的右上角页面加入新的界面,通过按钮来实现页面的切换。这里我们需要查阅软件中最上方帮助中的指令集以及资料中心,在软件中写所需要的代码。(很短,很容易上手)
(6)上位机编程中常用的指令汇总(实现小项目绰绰有余)
eg:
切换页面: page 页面名称 (page page1)即切换到page1页面
发送十六进制指令 : printh 01 即发送0x01
两者相结合即可完成我们独立按键的功能。
以19年电赛D题简易电路特性测试仪为例
初步设计首页如下:

首页只添加了3个按钮控件和一个文本控件。文本控件显示我们本次工程的名称,而按键就可控制我们的模式选择。所以我们需要添加3个子页面(测量模式,故障测试模式,幅频特性曲线显示模式)通过主页面的按键来切换页面。所以在主页面下分别点击对应按钮控件,下方会出现事件界面,我们通常在弹起事件中做操作,也可以达到一个防误触的效果。如下图所示,我们在事件内容中写了两行代码。
1.printh 01 即点击该按钮串口屏就会发送0x01(有大作用,我们可以在单片机串口中断中解析该数据,并做相应的操作。具体示例在单片机编程部分)
2page page0 即点击该按钮切换到page 0页面

但我们在切换的页面后如何返回呢?

其实我们的整个界面就如同一个按钮一样,也有弹起操作。那我们要返回主页面是不是就要点击子页面的整个页面,而后在其弹起事件中编写page main即可实现返回的操作。
重要提醒:❗❗❗ 如果我们的文本控件默认设置的是私有变量,那我们切换界面时其显示的文本为你初始的文本。所以我们与单片机通信使其显示数值的文本控件应设置为全局变量(如下图t3,t4,t5文本控件)。这样我们切换页面时数据就不会丢失。
我们尽量将显示的固定文字在上位机中设计完成,单片机和串口屏之间尽量不要传汉字,那样占用资源而且容易出错。

到此上位机所要干的事情我们都已经做完了。当然上位机中还有很多好玩的控件,待网友们去开发去尝试。串口屏可谓功能之强大。
stm32软件部分总体分为发送数据和接收数据。
发送数据
既然我们要发送数据我们就要符合相应的通讯协议,在我们发送数据,总要有个结尾标志,如果没有的话单片机就不知道你的数据发没发送完,从而卡死出不来。
🔰🔰🔰所以第一个重点就是要有结束符,向串口屏发送数据完要加结束符(连续三个0xff)那么当串口屏读取到连续三个0xff时这次的数据就已经发送完成从而跳出循环。不多说了,直接上代码。
/**
███████╗ ██ ██ ██╗ ██╗
██╔════╝ ║██████║ ██║ ██║
██║ ╚══██╔═╝ ███████║
██║ █████ ████████╗ ██║ ██╔══██║
██╚════██║ ╚═══════╝ ██║ ██║ ██║
█████████║ ██║ ██║ ██║
╚════════╝ ╚═╝ ╚═╝ ╚═╝
* @brief main
* @language C
* @harfware MicroController
* @version v1.0
* @date 29-July-2021
* @author Yuhang Gu
*
*/
首先进行串口配置(串口初始化)我使用的是stm32的USART3,所以硬件连接应为
RX------------PB10
TX------------PB11
void uart3_init(u32 bound)
{
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//时钟GPIOB、USART3
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
//USART1_TX PB10
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//USART1_RX PB11
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
//USART 初始化设置
USART_InitStructure.USART_BaudRate = bound;//一般设置为9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART3, &USART_InitStructure);
USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);//开启中断
USART_Cmd(USART3, ENABLE); //使能串口
}
串口初始化完成后波特率为9600,我们即可编写发送数据函数如下所示:
函数功能:发送字符串
void HMISends(char *buf1) //字符串发送函数
{
u8 i=0;
while(1)
{
if(buf1[i]!=0)
{
USART_SendData(USART3,buf1[i]); //发送一个字节
while(USART_GetFlagStatus(USART3,USART_FLAG_TXE)==RESET){};//等待发送结束
i++;
}
else
return ;
}
}
函数功能:连续3次发送一个字节(一般用来发送0xff作为结束符)
void HMISendb(u8 k) //字节发送函数
{
u8 i;
for(i=0;i<3;i++)
{
if(k!=0)
{
USART_SendData(USART3,k); //发送一个字节
while(USART_GetFlagStatus(USART3,USART_FLAG_TXE)==RESET){};//等待发送结束
}
else
return ;
}
}
在程序初始化中我们要对串口屏也进行一个初始化防止被之前没有进行完的数据传输所影响。
以下是串口屏的启动函数,写在初始化中即可。
void HMISendstart(void)
{
delay_ms(200);
HMISendb(0xff);
delay_ms(200);
}
在所有函数都写好之后,我们即可在keil中编写程序,发送我们想要的数据给串口屏。
一个小技巧✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅
C语言中有一个函数可以将其他类型强制转化为字符串类型,我们知道串口屏通信的话其实都是通过发送字符串来让串口屏显示我们想要的内容的。所以我们必须将我们的内容先转化为字符串。例如单片机自带的ADC所读取到的数值,连接温湿度传感器模块读取到的温湿度等等我们想要实时变化的数据显示到串口屏时我们就要用到sprintf函数。
sprintf函数使用方法如下图所示,
unsigned char buf[64];
sprintf((char *)buf,"page0.t3.txt=\"%.1f\"",Ri);
我们所要转化成的字符串其实就和普通的printf函数格式相同。若为整形则为%d,浮点型则为%f。Ri就是你所变化的数值,可以为ADC读到的值,温湿度,频率,电压等等
所以我们想要完整的发送数据给串口屏我们就可以这样操作。
sprintf((char *)buf,"page0.t3.txt=\"%.1f\"",Ri); //强制类型转化,转化为字符串
HMISends((char *)buf); //发送Ri的数据给page0页面的t3文本控件
HMISendb(0xff);//结束符
这样我们就可以让串口屏显示我们想要的数据了。
接收数据
单片机接收串口屏发送来的指令是在串口中断中进行的,我们在上位机使用时曾在按钮中编写printh 01,printh 函数发送指令后自带0x0d 0x0a结尾。每当按下我们单片机就会收到0x01这个数据,所以我们就是对接收到的消息进行分析做对应的操作就ok了。
void USART3_IRQHandler(void)
{
u8 Res;
if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Res =USART_ReceiveData(USART3); //读取接收到的数据
if(Res==0x01) Mode_MeasureFlag=1;
else if(Res==0x02) Mode_CorrectFlag=1;
else if(Res==0x03) Mode_MeasureFlag=0;
else if(Res==0x10) MeasureRi_Flag=1;
else if(Res==0x11) MeasureRo_Flag=1;
else if(Res==0x12) MeasureAv_Flag=1;
}
}
这样我们就通过串口屏来控制我们所要的模式了。例如进入测量模式,进入故障检测模式。
以下是我在备战2021电赛训练时做的19年D题简易电路特性测试仪所编写的部分代码,为的是给大家抛砖引玉一下,知道串口屏应该怎么用,怎么能够通过这小小的屏幕实现很强大的功能。当然数据的接收不止这么简单。我的使用一般就是通过接受的数据来改变功能模式选择的标志位,其实就可以实现大部分想要的功能。串口屏还可以作为键盘输入,以及计算器等,那我们单片机的数据接受就要更加的复杂,要对数据进行位操作解析。感兴趣的可以尝试一下,我就不再赘述。
while(1)
{
if(Mode_MeasureFlag==1)
{
/*************测量Ri*************************/
if(MeasureRi_Flag==1)
{
Key3=0;
Key1=0;
delay_ms(1000);
ADC_Temp1 =(float) ADC_ConvertedValue[1]/4096*3.3;
printf("\r\n ADC_Temp1 = %f \r\n",ADC_Temp1);
Key1=1;
delay_ms(5000);
// delay_ms(7000);
ADC_Temp2 =(float) ADC_ConvertedValue[1]/4096*3.3;
printf("\r\n ADC_Temp2 = %f \r\n",ADC_Temp2);
Ri=(ADC_Temp2*1000)/(ADC_Temp1-ADC_Temp2);
printf("\r\n Ri = %f \r\n",Ri);
sprintf((char *)buf,"page0.t3.txt=\"%.1f\"",Ri); //强制类型转化,转化为字符串
HMISends((char *)buf); //发送Ri的数据给page0页面的t3文本控件
HMISendb(0xff);//结束符
Key1=0;
MeasureRi_Flag=0;
}
/*************测量Ro*************************/
if(MeasureRo_Flag==1)
{
Key1=1;
Key3=1;
delay_ms(5000);
Key2=0;
ADC_Temp1 =(float) ADC_ConvertedValue[1]/4096*3.3;
printf("\r\n ADC_Temp1 = %f \r\n",ADC_Temp1);
Key2=1;
delay_ms(5000);
ADC_Temp2 =(float) ADC_ConvertedValue[1]/4096*3.3;
printf("\r\n ADC_Temp2 = %f \r\n",ADC_Temp2);
Ro=((ADC_Temp1-ADC_Temp2)*5100)/ADC_Temp2;
printf("\r\n Ro = %f \r\n",Ro);
sprintf((char *)buf,"page0.t4.txt=\"%.1f\"",Ro);
HMISends((char *)buf);
HMISendb(0xff);
Key2=0;
Key3=0;
MeasureRo_Flag=0;
}
/*************测量Av*************************/
if(MeasureAv_Flag==1)
{
Key1=1;
Key3=0;
delay_ms(5000);
ADC_Temp1 =(float) ADC_ConvertedValue[1]/4096*3.3;
printf("\r\n ADC_Temp1 = %f \r\n",ADC_Temp1);
Key3=1;
delay_ms(5000);
// delay_ms(7000);
ADC_Temp2 =(float) ADC_ConvertedValue[1]/4096*3.3;
printf("\r\n ADC_Temp2 = %f \r\n",ADC_Temp2);
Av=(ADC_Temp2/(ADC_Temp1/105.8));
printf("\r\n Ro = %f \r\n",Av);
sprintf((char *)buf,"page0.t5.txt=\"%.1f\"",Av);
HMISends((char *)buf);
HMISendb(0xff);
Key2=0;
Key3=0;
MeasureAv_Flag=0;
}
}
}
在程序中直接调用即可(把想要显示的变量换掉就ok)
1.文本控件(.txt)
sprintf((char *)buf,"page0.t1.txt=\"%d\"",num); //显示变化的数值
HMISends((char *)buf);
HMISendb(0xff);
sprintf((char *)buf,"page1.t0.txt=\"Wait...\"");//显示字符串
HMISends((char *)buf);
HMISendb(0xff);
2.数字控件(.val)
sprintf((char *)buf,"n0.val=%d",Target_Speed);
HMISends((char *)buf);
HMISendb(0xff);
3.曲线控件(add 控件ID,选择通道,数值)数值取值范围(0-255)通道数量,波形颜色在上位机中设置。
🔲我们所要发送的数据不在此范围内的话要进行数值等比缩小,并且只能发送整数。
sprintf((char *)buf,"add 1,1,%d",ExchangeSpeed1);
HMISends((char *)buf);
HMISendb(0xff);
串口屏的功能远不止如此,大佬们有兴趣可以多去研究研究,熟练使用串口屏基本能够替代oled、tft等各大屏幕。通过上位机以及代码的配合可以实现更多的功能,开发简单,易上手。以上是我的各人理解,分享我在使用过程中的一些小技巧,如果有问题的话大家可以一起讨论。
本人开学大三学生一枚,备战电赛中,之后会陆续写一些小项目的教程如风力摆,平衡车,纸张计数等如果有需要请关注我谢谢!😁🤗
这可能是个愚蠢的问题。但是,我是一个新手......你怎么能在交互式rubyshell中有多行代码?好像你只能有一条长线。按回车键运行代码。无论如何我可以在不运行代码的情况下跳到下一行吗?再次抱歉,如果这是一个愚蠢的问题。谢谢。 最佳答案 这是一个例子:2.1.2:053>a=1=>12.1.2:054>b=2=>22.1.2:055>a+b=>32.1.2:056>ifa>b#Thecode‘if..."startsthedefinitionoftheconditionalstatement.2.1.2:057?>puts"f
文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,
LL库和HAL库简介LL:Low-Layer,底层库HAL:HardwareAbstractionLayer,硬件抽象层库LL库和hal库对比,很精简,这实际上是一个精简的库。LL库的配置选择如下:在STM32CUBEMX中,点击菜单的“ProjectManager”–>“AdvancedSettings”,在下面的界面中选择“AdvancedSettings”,然后在每个模块后面选择使用的库总结:1、如果使用的MCU是小容量的,那么STM32CubeLL将是最佳选择;2、如果结合可移植性和优化,使用STM32CubeHAL并使用特定的优化实现替换一些调用,可保持最大的可移植性。另外HAL和L
功能需求:主机使用一个串口,与两个从机进行双向通信,主机向从机发送数据,从机能够返回数据,由于结构限制,主机与从机之间只有3根线(电源、地、数据线),并且从机上没有设物理的电源开关,需要通过与主机连接的数据线来控制开机,总结如下:1、数据线只有1根2、能够双向通信3、主机能够控制从机开机4、主机可以单独向1个从机发数据,也可以同时向两个从机发送数据根据需求,设计出如下电路:工作原理分析:VCC_24V_IN、GND、LINE_L(LINE_R)三根线接线连接到从机,电源开启电路是从机内部的电源控制。开机的逻辑:*主机先上电,LINE_L因为主机的R1上拉而有高电平,使Q6导通,Q5的G极电压被
目录一、ESP32简单介绍二、ESP32Wi-Fi模块介绍三、ESP32Wi-Fi编程模型四、ESP32Wi-Fi事件处理流程 五、ESP32Wi-Fi开发环境六、ESP32Wi-Fi具体代码七、ESP32Wi-Fi代码解读6.1主程序app_main7.2自定义代码wifi_init_sta()八、ESP32Wi-Fi连接验证8.1测试方法8.2服务器模拟工具sscom58.3测试代码8.4测试结果前言为了开发一款亚马逊物联网产品,开始入手ESP32模块。为了能够记录自己的学习过程,特记录如下操作过程。一、ESP32简单介绍ESP32是一套Wi-Fi(2.4GHz)和蓝牙(4.2)双模解决方
有道无术,术尚可求,有术无道,止于术。本系列SpringBoot版本3.0.4本系列SpringSecurity版本6.0.2本系列SpringAuthorizationServer版本1.0.2源码地址:https://gitee.com/pearl-organization/study-spring-security-demo文章目录前言1.OAuth2AuthorizationServerMetadataEndpointFilter2.OAuth2AuthorizationEndpointFilter3.OidcProviderConfigurationEndpointFilter4.N
在我的代码中,我需要使用各种算法(包括CRC32)对文件进行哈希处理。因为我还在Digest系列中使用其他加密哈希函数,所以我认为为它们维护一个一致的接口(interface)会很好。为了记录,我确实找到了digest-crc,一颗完全符合我要求的gem。问题是,Zlib是标准库的一部分,并且有一个我想重用的CRC32工作实现。此外,它是用C编写的,因此它应该提供与digest-crc相关的卓越性能,后者是纯ruby实现。实现Digest::CRC32一开始看起来非常简单:%w(digestzlib).each{|f|requiref}classDigest::CRC32一切正常:
我正在尝试在我的机器上安装win32-apigem,但在构建native扩展时我遇到了一些问题:$geminstallwin32-api--no-ri--rdocTemporarilyenhancingPATHtoincludeDevKit...Buildingnativeextensions.Thiscouldtakeawhile...C:\Programs\dev_kit\bin\make.exe:***Couldn'treservespaceforcygwin'sheap,Win32error0ERROR:Errorinstallingwin32-api:ERROR:Failed
我在Windows上运行ruby1.9.2并试图移植在Ruby1.8中工作的代码。该代码使用以前运行良好的Open4.popen4。对于1.9.2,我做了以下事情:通过geminstallPOpen4安装了POpen4需要POpen4通过require'popen4'尝试像这样使用POpen4:Open4.popen4("cmd"){|io_in,io_out,io_er|...}当我这样做时,我得到了错误:nosuchfiletoload--win32/open3如果我尝试安装win32-open3(geminstallwin32-open3),我会收到错误消息:win32-op
DellInspiron5488加内存32G 原装内置内存仅仅8G,目前看,真的太小了! 1.内存型号Dell5488内存型号:DDR42666。笔记本有两个内存插槽,原装占了一个,还能扩展一个。 2.买内存如果买Dell原装笔记本内存,8G就得500块左右。 我咨询了一下,三星的笔记本内存,可以兼容。16G,299块(2023年2月23日,京东价) Dell5488内存组合,最多只能插两根16G内存。 我于是买了两根三星16G内存。装上,很爽😄 跑国产系统统信UOS,再也看不到用交换区了,32G内存,爽!