1.GD32F103VET6介绍
GD32系列单片机和STM32系列单片机在应用上十分类似,需要注意的是本系统GD32的最大时钟频率是108MHz。本系统的功能是实现LORA网关,GD32F103VET6相较于STM32系列单片机的性价比更高。
GD32F103VET6是一款基于Arm®的32位通用微控制器,其核心是Cortex®-M3 RISC内核。这款微控制器在处理能力方面具有最佳比率,同时降低了功耗和外设消耗。GD32F103VET6可运行在108 MHz的频率下,并且具有闪存访问零等待状态以获取最大效率的特点。它提供高达3 MB的片上闪存和高达96 KB的SRAM存储器,以及广泛的连接到两条APB总线的一系列增强型I/O和外围设备。该器件适用于广泛的应用领域,包括工业控制、电机驱动、电力监控和报警系统,以及消费类和手持设备、POS、车载GPS、可视对讲、PC外围设备等12。其工作电压范围为2.6至3.6 V,温度范围为-40至+85 °C,且提供了多种省电模式以优化功耗和唤醒延迟,这对于低功率应用来说尤为重要13。此外,GD32F103VET6还包含多个ADC和DAC,通用定时器,以及多种通信接口,如SPI、I2C、USART、UART、I2S、USBD、CAN等,使其能够满足各种复杂的通信和控制需求。
2. BX_6K1字符卡
BX_6K1是一种仰邦科技生产的一种LED控制卡,本系统用BX_6K1字符卡控制96*16的LED双色显示屏实现指定内容的显示和超阈值警示。BX_6K系列字符卡有基于6K1款的升级版,其中有支持网口和 4G模块的升级款字符卡,本系统采用的是基础班的字符卡,用它带的RS485接口和单片机进行通信。BX_6K1有以下特点:
- 支持单色、双色、三基色屏,支持信息实时更新;
- 提供通讯协议,专用于工业现场,设备配套、车载显示,智能公交站牌和集群显示系统等领域的二次开发;
- 兼容6K协议,简化开发难度;
- 更高刷新技术,更丰富的显示效果,实时更新信息时画面稳定无闪烁;
- 对于大中型集群项目板载通讯接口,显示接口和面向各种特殊应用系统,灵活地满足用户的特殊需求;
- 控制面积灵活,显示功能丰富,性价比超高;
- 支持字库内码格式的信息传输方式,并且支持图片点阵格式的信息传输方式;
- 标配亮度、温度、温湿度传感器和红外遥控头接口座。目前已经实现温度、温湿度接口,其他接口根据项目需要可考虑支持。
- 支持GBK编码的繁体字库,当然也支持GB2312的编码。只需下载相应字库并设置相应参数即可。
- 工艺精良,品质稳定。3.5V-6V宽电压工作,-40℃~80℃宽环境温度;
3.操作说明
3.1 BX_6K1字符卡相关软件的使用
LedShowZk是仰邦科技针对BX系列字符卡的可视化操作工具,通过此软件可以向字符卡内烧录字库和固件,通过在软甲内创建“节目”并发送可以实现指定内容在LED屏幕上的显示。需要注意的是首次使用软甲时需要配置好通讯参数,否则会通讯失败。BX_6K1预留的有232端子接口和485端子接口,两个接口都能实现上位机和字符卡的通讯,前提是设置好通讯参数!用一条USB转232数据线将电脑主机和BX_6K1的232口连接,需要注意的是通信引脚要接线准确。连线正确后进入软件配置屏幕参数(默认密码“168”),如图3.1所示。
图3.1 屏参配置
屏参配置完成后,在屏幕中创建节目和字库测试字符卡和LED屏幕是否适配和能否正常通信。测试过程如图3.2所示,LED显示文本内容则通讯成功。
3.2 发送内容至LED屏幕
创建动态分区并发送的屏幕上,如果系统需要显示实时更新的内容,建议创建动态分区,BX_6K1支持发送实时显示内容和节目(掉电不丢失)。本系统要显示的数据是实时更新的,所以就设置了动态分区并发送到屏幕上。通过设置选项里面的“字库维护”可以烧录自己想要的字体大小和样式,建议选择14*14点阵大小的汉字和14*14大小的英文。
注:LedShowSuite也是仰邦科技提供的可视化软件,二者是通用的,LedShowSuite的有点就是能创建自己想要字体的字库,其中也包括旋转字体的字库。
3.2 BX_6K1通信协议说明
本系统使用的是系统中预留好的485接口,将BX_6K1的RS485端子上的D+连板子A口,D-连板子B口。
BX_6K1有专门的通信协议用于控制字符卡的相关操作,在仰邦科技官网可以下载相关文档。BX_6k和BX_5K系列字符卡的相关协议是相通的,解析起来并不困难。协议的标准格式由8个字节的帧头、14个字节的包头、可变长度的数据、2个字节的包校验和一个字节的帧尾组成。标准通讯格式如图3.3所示。
图3.3 帧标准格式
本章节着重介绍发送显示实时信息的帧格式,注意低位在前,高位灾后:a5 a5 a5 a5 a5 a5 a5 a5 fe ff 00 80 00 00 00 00 00 00 fe 02 2c 00 a3 06 01 00 00 00 01 23 00 00 00 80 00 00 80 80 20 00 00 00 00 0a 00 00 00 00 02 01 01 00 02 0a 08 00 00 00 bb b6 d3 ad b9 e2 c1 d9 3c 49 5a
4.相关代码
本系统使用的是控制板预留的485的1号接口,使用的是此端口的相关发送函数来实现对字符卡的控制,BX_6K1的驱动代码如下:
#include "app_bx6k1.h"
uint8_t Bx_header[8] = {0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5};//帧头
size_t array_len = 0;
/******************************************************************************
* @函数名: BX6K1_Show_String(char *string_show,uint16_t Dstaddr)
* @功 能: 显示指定信息 -- 要显示指定颜色的时候记得添加转义字符
* @输 入: string_show--要显示的字符串 Dstaddr-屏地址
* @返 回: NULL
* @备 注:"\C1abc\C2abc" -- 显示 红色的abc和绿色的abc
******************************************************************************/
void BX6K1_Show_String(char *String_show,uint16_t Dstaddr)
{
uint8_t *data;
int crc16_L,crc16_H=0;
int len=0;
uint8_t led_show[128] = {0};
data = string_to_uint8_t_array(String_show,&array_len);
memcpy(led_show,Bx_header,8); //帧头
len+=8; //偏移的长度
//包头数据
led_show[len++] = Dstaddr&0xff;
led_show[len++] = Dstaddr>>8;
led_show[len++] = header_SrcAddr&0xff;
led_show[len++] = header_SrcAddr>>8;
led_show[len++] = header_Reserved;
led_show[len++] = header_Reserved;
led_show[len++] = header_Reserved;
led_show[len++] = header_BarCode;
led_show[len++] = header_CheckMode;
led_show[len++] = header_DisplayMode;
led_show[len++] = header_DeviceType;
led_show[len++] = header_ProtocolVersion;
led_show[len++] = (array_len+36)&0xff; //数据区长度
led_show[len++] = (array_len+36)>>8; //数据区长度
//数据区
led_show[len++] = cmd_Group3; //命令分组编号
led_show[len++] = Num6; //命令编号
led_show[len++] = req_yes; //是否要求控制器回复
led_show[len++] = default_value; //不清区域
led_show[len++] = default_value; //保留
led_show[len++] = 0xff; //删除区域个数
led_show[len++] = Num1; //更新区域个数
led_show[len++] = (array_len+27)&0xff; //区域0数据长度
led_show[len++] = (array_len+27)>>8; //区域0数据长度
//区域0数据
led_show[len++] = region_type; //区域类型
led_show[len++] = 0x00; //x坐标
led_show[len++] = 0x00;
led_show[len++] = 0x00; //y坐标
led_show[len++] = 0x00;
led_show[len++] = 128; //区域宽度单位 80-- 像素点 0--8个像素点
led_show[len++] = 0x80;
led_show[len++] = 16; //区域高度
led_show[len++] = 0x00;
led_show[len++] = 0x00; //动态区域编号
led_show[len++] = 0X00; //行间距
led_show[len++] = 0X00; //动态区数据循环显示
led_show[len++] = 0X0A; //超时时间
led_show[len++] = 0X00; //超时时间
led_show[len++] = 0x00; //不使能语音模块
led_show[len++] = 0x00; //拓展位数
led_show[len++] = 9; //字体对齐方式 --上下居中 左右右对齐
led_show[len++] = 0x02; //是否多行显示
led_show[len++] = 0x01; //不自动换行
led_show[len++] = 0x01; //静止显示
led_show[len++] = 0x00; //退出方式
led_show[len++] = 0x02; //显示速度
led_show[len++] = 0x0a; //特技显示时间
led_show[len++] = array_len&0xff; //显示内容长度
led_show[len++] = array_len>>8;
led_show[len++] = array_len>>16;
led_show[len++] = array_len>>24;
for(int i = 0;i<array_len;i++)
{
led_show[len++] = data[i]; //显示内容
}
crc16_L = crc16_ibm(&led_show[8],len-8)&0xff;
crc16_H = crc16_ibm(&led_show[8],len-8)>>8;
led_show[len++] = crc16_L;
led_show[len++] = crc16_H;
led_show[len++] = data_end;
for(int j=0;j<len;j++)
{
printf("led_show[%d] = %x\r\n",j,led_show[j]);
}
RS4851Send(led_show,len);
}
/******************************************************************************
* @函数名: BX6K1_Power(uint8_t cmd,uint16_t Dstaddr)
* @功 能: 指定屏的电源开关
* @输 入: cmd--开关命令码 Dstaddr-屏地址
* @返 回: NULL
* @备 注:
******************************************************************************/
void BX6K1_Power(uint8_t Cmd,uint16_t Dstaddr)
{
int crc16_L,crc16_H=0;
int len=0;
uint8_t led_show[128] = {0};
memcpy(led_show,Bx_header,8); //帧头
len+=8;
//包头数据
led_show[len++] = Dstaddr&0xff;
led_show[len++] = Dstaddr>>8;
led_show[len++] = header_SrcAddr&0xff;
led_show[len++] = header_SrcAddr>>8;
led_show[len++] = header_Reserved;
led_show[len++] = header_Reserved;
led_show[len++] = header_Reserved;
led_show[len++] = header_BarCode;
led_show[len++] = header_CheckMode;
led_show[len++] = header_DisplayMode;
led_show[len++] = 0xfe;
led_show[len++] = header_ProtocolVersion;
led_show[len++] = power_len&0xff; //数据区长度
led_show[len++] = power_len>>8; //数据区长度
led_show[len++] = cmd_Group3;
led_show[len++] = Num0;
led_show[len++] = req_yes;
led_show[len++] = default_value;
led_show[len++] = default_value;
led_show[len++] = Cmd;
crc16_L = crc16_ibm(&led_show[8],len-8)&0xff;
crc16_H = crc16_ibm(&led_show[8],len-8)>>8;
led_show[len++] = crc16_L;
led_show[len++] = crc16_H;
led_show[len++] = data_end;
for(int i=0;i<len;i++)
{
printf("led_show[%d] = %x\r\n",i,led_show[i]);
}
RS4851Send(led_show,len);
}
/******************************************************************************
* @函数名: BX6K1_SetLight(uint8_t Light_strength,uint8_t Dstaddr,uint8_t Set_mode,uint8_t *Light_period)
* @功 能: 设置屏幕亮度 -- 强制/定时设置
* @输 入: Light_strength--光强度 Dstaddr-屏地址 Set_mode--设置模式 Light_period--定时时段光照 一天分48分段
* @返 回: NULL
* @备 注:
******************************************************************************/
void BX6K1_SetLight(uint8_t Light_strength,uint8_t Dstaddr,uint8_t Set_mode,uint8_t *Light_period)
{
int crc16_L,crc16_H=0;
int len=0;
uint16_t send_length = 0;
uint8_t led_show[128] = {0};
memcpy(led_show,Bx_header,8); //帧头
len+=8;
led_show[len++] = Dstaddr&0xff;
led_show[len++] = Dstaddr>>8;
led_show[len++] = header_SrcAddr&0xff;
led_show[len++] = header_SrcAddr>>8;
led_show[len++] = header_Reserved;
led_show[len++] = header_Reserved;
led_show[len++] = header_Reserved;
led_show[len++] = header_BarCode;
led_show[len++] = header_CheckMode;
led_show[len++] = header_DisplayMode;
led_show[len++] = 0xfe; //通配符
led_show[len++] = header_ProtocolVersion;
if(Set_mode == 0x01)
send_length = 7;
else
send_length = 55;
led_show[len++] = send_length&0xff;
led_show[len++] = send_length>>8;
led_show[len++] = cmd_Group3;
led_show[len++] = Num2;
led_show[len++] = req_yes;
led_show[len++] = default_value;
led_show[len++] = default_value;
led_show[len++] = Set_mode;
switch(Set_mode)
{
case 0x01:
{
led_show[len++] = Light_strength;
break;
}
case 0x02:
{
led_show[len++] = 0x00;
memcmp(&led_show[len],Light_period,48);
len+=48;
break;
}
default :
printf("input mode error!\r\n");
}
crc16_L = crc16_ibm(&led_show[8],len-8)&0xff;
crc16_H = crc16_ibm(&led_show[8],len-8)>>8;
led_show[len++] = crc16_L;
led_show[len++] = crc16_H;
led_show[len++] = data_end;
RS4851Send(led_show,len);
// for(int i = 0;i<len;i++)
// {
// printf("led_show[%d] = %x\r\n",i,led_show[i]);
// }
}
//屏幕系统复位
/******************************************************************************
* @函数名: void BX6K1_Reset(uint8_t Dstaddr)
* @功 能: 屏幕系统复位
* @输 入: Dstaddr-屏地址
* @返 回: NULL
* @备 注:
******************************************************************************/
void BX6K1_Reset(uint8_t Dstaddr)
{
int crc16_L,crc16_H=0;
int len=0;
uint16_t send_length = 0;
uint8_t led_show[128] = {0};
memcpy(led_show,Bx_header,8); //帧头
len+=8;
led_show[len++] = Dstaddr&0xff;
led_show[len++] = Dstaddr>>8;
led_show[len++] = header_SrcAddr&0xff;
led_show[len++] = header_SrcAddr>>8;
led_show[len++] = header_Reserved;
led_show[len++] = header_Reserved;
led_show[len++] = header_Reserved;
led_show[len++] = header_BarCode;
led_show[len++] = header_CheckMode;
led_show[len++] = header_DisplayMode;
led_show[len++] = 0xfe; //通配符
led_show[len++] = header_ProtocolVersion;
led_show[len++] = Reset_len&0xff;
led_show[len++] = Reset_len>>8;
led_show[len++] = 0xA2;
led_show[len++] = 0x01;
led_show[len++] = 0x01;
led_show[len++] = 0x00;
led_show[len++] = 0x00;
crc16_L = crc16_ibm(&led_show[8],len-8)&0xff;
crc16_H = crc16_ibm(&led_show[8],len-8)>>8;
led_show[len++] = crc16_L;
led_show[len++] = crc16_H;
led_show[len++] = data_end;
RS4851Send(led_show,len);
}
//读指定屏幕地址的ID
void BX6K1_ReadID(uint16_t Dstaddr)
{
}
//控制卡返回的信息 -- -1--错误帧 0--无错误
uint8_t RecFrom_BX6K1(uint8_t *data)
{
// int crc16_L,crc16_H=0;
// int len=0;
uint8_t CmdError = 0; //命令处理状态
int cmpres = memcmp(Bx_header,data,8);
//包头数据
if(cmpres == 0)
{
if(data[8]==0xA5&&data[30]==0x5A)
{
CmdError = data[25]; //提取错误码
}
else
CmdError = -1;
}
else
CmdError = -1;
return CmdError;
}
/******************************************************************************
* @函数名: crc16_modbus(uint8_t *data, size_t length)
* @功 能: 生成crc16校验位
* @输 入: data--要校验的数组 length-长度
* @返 回: 校验结果
* @备 注:
******************************************************************************/
uint16_t crc16_ibm(const uint8_t* data, uint16_t length)
{
uint16_t crc = 0x0000;
uint16_t polynomial = 0xA001;
for (uint16_t i = 0; i < length; i++)
{
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x0001) {
crc = (crc >> 1) ^ polynomial;
} else {
crc = crc >> 1;
}
}
}
return crc;
}
/******************************************************************************
* @函数名: string_to_uint8_t_array(const char* str, size_t* length)
* @功 能: 字符转uint8数组
* @输 入: str -- 字符串 length--字符串的长度
* @返 回: 转化得到的uint8_t数组
* @备 注:
******************************************************************************/
uint8_t* string_to_uint8_t_array(const char* str, size_t* length) {
if (str == NULL) {
return NULL;
}
size_t str_length = strlen(str);
uint8_t* ascii_array = (uint8_t*)malloc(str_length * sizeof(uint8_t));
if (ascii_array == NULL) {
return NULL;
}
for (size_t i = 0; i < str_length; ++i) {
ascii_array[i] = (uint8_t)str[i];
}
*length = str_length;
return ascii_array;
}
在本系统中还移植了FreeRtos实时操作系统,在485发送任务中调用LED显示接口函数查看现象 ,以下是485发送任务的相关代码
void Pro485Task(void* p)
{
int res = 0;
BaseType_t xReturn = pdTRUE;
p4851 = ComToUart(rs4851_port);
p4852 = ComToUart(rs4852_port);
Rs485Init();
RS485BinarySem_Handle = xSemaphoreCreateBinary();
if(NULL != RS485BinarySem_Handle)
{
DEBUG("RS485BinarySem_Handle create success!\r\n");
p4851->callflag = 0;
p4851->recvcallback = Pro4851_callback;
p4852->callflag = 0;
p4852->recvcallback = Pro4852_callback;
}
else
{
DEBUG("RS485BinarySem_Handle create error!\r\n");
while(1)
{
vTaskDelay(1000);
}
}
BX6K1_Show_String("\\C2深度0·01米",1);
bsp_DelayMS(2000);
BX6K1_SetLight(5,1,0x01,0);
// BX6K1_Power(power_open,1);
while(1)
{
#if DEGUB_STACK
printf("Stack Pro485Task:%d\n", uxTaskGetStackHighWaterMark(Pro485_Handle));
#endif
xReturn = xSemaphoreTake(RS485BinarySem_Handle, portMAX_DELAY);
if(pdTRUE == xReturn) //接收到信号量
{
if(onceflag==0){
onceflag=1;
IDWG_Symbol=0xFF;
}
if(p4851->callflag == 1)
{
DEBUG("4851 recv Semaphore\r\n");
Pro485_rxlen = RS4851Recv(Pro485_rxbuf, PRO485_MAX_LEN);
if(Pro485_rxlen)
{
res = ProData(1, Pro485_rxbuf, Pro485_rxlen);
memset(Pro485_rxbuf, 0, PRO485_MAX_LEN);
p4851->callflag = 0;
}
}
if(p4852->callflag == 1)
{
DEBUG("4852 recv Semaphore\r\n");
Pro485_rxlen = RS4852Recv(Pro485_rxbuf, PRO485_MAX_LEN);
if(Pro485_rxlen)
{
res = ProData(2, Pro485_rxbuf, Pro485_rxlen);
memset(Pro485_rxbuf, 0, PRO485_MAX_LEN);
p4852->callflag = 0;
}
}
}
xEventGroupSetBits(xCreatedEventGroup, 0x10);
}
}
正确显示现象应该显示 绿色 “深度0.01米” 如图4.1所示,LED屏幕显示正确。