本帖最后由 yljon 于 2020-6-28 20:42 编辑
基于RT-Thread与东软载波的上位机下位机开发
作者:李俊(论坛ID:yljon)
一、概述:
1. 下位机系统实现功能:
1) 电参数自动采集,演示系统用开发板上VR采集ADC,实际系统中用RS-485采配电房交流2000A配电柜;
2) A/B路合闸开关切换控制,演示系统用开发板上K1,K2,实际系统用隔离IO采集合闸开关;
3) A/B路合闸开关状态显示,演示系统用开发板上LED,实际系统用隔离IO输出;
4) 触摸屏显示电流和A/B路状态,触摸屏也可以操作A/B路合闸开关切换;
2. 上位机系统实现功能:
1) 采集的数据上传到电脑上位机程序中,在上位机程序显示采集数据和状态;
2) 上位机程序下发控制指令,使A/B路合闸开关进行动作;
3. 下位机准备用东软载波开发板,MCU型号:ES32F0654LT;
4. 下位机系统准备用RT-Thread;
二、RT-Thread使用情况概述:
首次接触RT-Thread,感觉比UCOS比FreeRTOS强大,RT-Thread 与其他很多 RTOS 如 FreeRTOS、uC/OS 的主要区别之一是,它不仅仅是一个实时内核,还具备丰富的中间层组件。详细内容参见RT-Thread官网:https://www.rt-thread.org/document/site/tutorial/quick-start/introduction/introduction/
我这个演示项目主要用RT-Thread的内容有:
1. 由于申请的板卡是M0的板卡,那就用RT-Thread精简版的吧,就用:RT-Thread Nano;
2. 虽然这个演示项目足够简单,多线程还是要用的;
3. 各线程里面的延时肯定用RT-Thread自带的,时钟管理要用滴;
4. 由于通信主要用UART,预计接收发送啥的应该用中断;
5. 开发板上的按钮,预计用中断吧;
其它RT-Thread高级功能估计这次用不上了,暂时就不去搞了。
三、硬件框架:
1. 硬件包括:
1) 开发板:东软载波开发板,MCU型号:ES32F0654LT
2) 烧录器:东软载波配套的ES-LINK II
3) 串口屏:型号TJC3224T124_011(这个也是向官方申请的样品)
2. 硬件连接示意图:
3. 实际硬件图:
四、软件框架:
1. 先将驱动装起来,由于此次是二姨家配套的板子提供的烧录器是ES-LINK II,到官网(http://www.essemi.com/)下载ES-LINK II的驱动ESburner_1.0.0.98.zip,安装好驱动之后,将ES-LINKII插入电脑,在电脑上会看到如下:
打开KEIL,在设置里看见这样,至此驱动安装就OK啦。
2.我的开发板是ES32F0654,要将PACK装起来,到官网(http://www.essemi.com/)上下载PACK,我下载的是Eastsoft.ES32_DFP.1.0.5,安装好之后,在KEIL中可以看见如下:
3.为了编程方便,先选择一个官网(http://www.essemi.com/)上的DEMO基础程序,我选择的是ES32_SDK_V1.05下的02_send_recv_by_interrupt,
4.下面就是重点了:移植RT-Thread Nano,精彩的部分即将开始:
1)点击 Manage Run-Time Environment,在 Manage Rum-Time Environment 里 "Software Component" 栏找到 RTOS,Variant 栏选择 RT-Thread,然后勾选 kernel,点击 "OK" 就添加 RT-Thread 内核到工程了。
2)这个时候将在工程里看见添加内容:
3)下面是让这个RT-Thread接管我们的裸机程序:
A. 异常处理函数的接管:删除原来裸机程序irq.c中原来的HardFault_Handler() ,这个在RT-Thread中有重新定义;
B.挂机处理函数的接管:删除原来裸机程序irq.c中原来的PendSV_Handler() ,这个在RT-Thread中有重新定义;
C.时钟处理函数的接管:删除原来裸机程序irq.c中原来的SysTick_Handler() ,这个在RT-Thread中有重新定义;
D. 在main.c 加入RT-Thread的头文件rtthread.h即可。
至此一个RT-Thread程序已经诞生了!!!
5.在移植好的程序里添加需要的功能:
1)添加数据采集功能:
这个系统是A路和B路切换功能,但是采集只需要采集一路即可,使用ADC采集,ADC初始化代码如下:
void adc_init(void)
{
gpio_init_t x;
/* Initialize ADC pin */
x.mode = GPIO_MODE_INPUT;
x.pupd = GPIO_PUSH_UP;
x.odrv = GPIO_OUT_DRIVE_NORMAL;
x.flt = GPIO_FILTER_DISABLE;
x.type = GPIO_TYPE_CMOS;
x.func = GPIO_FUNC_0;
ald_gpio_init(GPIO_CH_PORT, ADC_CH14_PIN, &x);
/* clear adc_handle_t structure */
memset(&h_adc, 0x0, sizeof(adc_handle_t));
/* clear adc_nch_conf_t structure */
memset(&nm_config, 0x0, sizeof(adc_nch_conf_t));
/* Initialize adc */
h_adc.perh = ADC0;
h_adc.init.data_align = ADC_DATAALIGN_RIGHT;
h_adc.init.scan_mode = DISABLE;
h_adc.init.cont_mode = DISABLE;
h_adc.init.nch_len = ADC_NCH_LEN_1;
h_adc.init.disc_mode = ADC_ALL_DISABLE;
h_adc.init.disc_nbr = ADC_DISC_NBR_1;
h_adc.init.conv_res = ADC_CONV_RES_12;
h_adc.init.clk_div = ADC_CKDIV_128;
// h_adc.init.nche_sel = ADC_NCHESEL_MODE_ALL;
h_adc.init.neg_ref = ADC_NEG_REF_VSS;
h_adc.init.pos_ref = ADC_POS_REF_VDD;
// h_adc.adc_reg_cplt_cbk = normal_convert_complete;
// h_adc.adc_inj_cplt_cbk = insert_convert_complete;
// h_adc.adc_out_of_win_cbk = out_window_complete;
// h_adc.adc_error_cbk = error_complete;
// h_adc.adc_ovr_cbk = ovr_complete;
ald_adc_init(&h_adc);
/* Initialize normal convert channel */
nm_config.channel = ADC_CHANNEL_14;
nm_config.rank = ADC_NCH_RANK_1;
nm_config.samp_time = ADC_SAMPLETIME_4;
ald_adc_normal_channel_config(&h_adc, &nm_config);
return;
}
在ADC初始化之后,就可以在主程序中直接使用了,直接将采集数据给myvolt即可,
代码如下:
ald_adc_start_by_dma(&h_adc, &myvolt, 1, ADC_CHANNEL_0);
2)由于我设计的数据采集系统支持本地按钮切换操作,按钮才操作使用GPIO的INPUT功能即可,为了方便使用,使用中断方式,GPIO初始化代码如下:
void key_init(void)
{
gpio_init_t x;
exti_init_t exti;
/* Initialize key exti pin as input */
x.mode = GPIO_MODE_INPUT;
x.odos = GPIO_PUSH_PULL;
x.pupd = GPIO_PUSH_UP;
x.odrv = GPIO_OUT_DRIVE_NORMAL;
x.flt = GPIO_FILTER_DISABLE;
x.type = GPIO_TYPE_CMOS;
x.func = GPIO_FUNC_1;
ald_gpio_init(GPIO_KEY_1_PORT, GPIO_KEY_1_PIN, &x);
ald_gpio_init(GPIO_KEY_2_PORT, GPIO_KEY_2_PIN, &x);
/* Initialize external interrupt */
exti.filter = ENABLE;
exti.cks = EXTI_FILTER_CLOCK_10K;
exti.filter_time = 10;
ald_gpio_exti_init(GPIO_KEY_1_PORT, GPIO_KEY_1_PIN, &exti);
ald_gpio_exti_init(GPIO_KEY_2_PORT, GPIO_KEY_2_PIN, &exti);
/* Clear interrupt flag */
ald_gpio_exti_clear_flag_status(GPIO_KEY_1_PIN);
ald_gpio_exti_clear_flag_status(GPIO_KEY_2_PIN);
/* Configure interrupt */
ald_gpio_exti_interrupt_config(GPIO_KEY_1_PIN, EXTI_TRIGGER_TRAILING_EDGE, ENABLE);
ald_gpio_exti_interrupt_config(GPIO_KEY_2_PIN, EXTI_TRIGGER_TRAILING_EDGE, ENABLE);
}
初始化完成之后,编写中断处理函数,由于分别使用A路和B路按钮,判断一下按钮即可,代码如下:
void gpio_irq_handler(void)
{
/* Check and clear interrupt flag */
if (ald_gpio_exti_get_flag_status(GPIO_KEY_1_PIN)) {
ald_gpio_exti_clear_flag_status(GPIO_KEY_1_PIN);
//ald_gpio_toggle_pin(LED1_PORT, LED1_PIN);
ald_gpio_write_pin(LED2_PORT,LED2_PIN,1);
//
//这里可以加入防呆
//
ald_gpio_write_pin(LED1_PORT,LED1_PIN,0);
FenLu=1;
}
if (ald_gpio_exti_get_flag_status(GPIO_KEY_2_PIN)) {
ald_gpio_exti_clear_flag_status(GPIO_KEY_2_PIN);
//ald_gpio_toggle_pin(LED2_PORT, LED2_PIN);
ald_gpio_write_pin(LED2_PORT,LED2_PIN,0);
//
//这里可以加入防呆
//
ald_gpio_write_pin(LED1_PORT,LED1_PIN,1);
FenLu=2;
}
return;
}
3)由于我设计这套系统支持触摸屏切换A路和B路,触摸屏使用串口,所以串口的处理如下,先初始化触摸屏的串口UART1,代码如下:
void uart1_init(void)
{
gpio_init_t x;
/* Initialize tx pin */
x.mode = GPIO_MODE_OUTPUT;
x.odos = GPIO_PUSH_PULL;
x.pupd = GPIO_PUSH_UP;
x.odrv = GPIO_OUT_DRIVE_NORMAL;
x.flt = GPIO_FILTER_DISABLE;
x.type = GPIO_TYPE_TTL;
x.func = GPIO_FUNC_3;
ald_gpio_init(UART1_TX_PORT, UART1_TX_PIN, &x);
/* Initialize rx pin */
x.mode = GPIO_MODE_INPUT;
x.odos = GPIO_PUSH_PULL;
x.pupd = GPIO_PUSH_UP;
x.odrv = GPIO_OUT_DRIVE_NORMAL;
x.flt = GPIO_FILTER_DISABLE;
x.type = GPIO_TYPE_TTL;
x.func = GPIO_FUNC_3;
ald_gpio_init(UART1_RX_PORT, UART1_RX_PIN, &x);
/* clear uart_handle_t structure */
memset(&h_uart1, 0x0, sizeof(h_uart1));
/* Initialize tx_buf */
memset(tx1_buf, 0x55, 32);
/* Initialize uart */
h_uart1.perh = UART1;
h_uart1.init.baud = 115200;
h_uart1.init.word_length = UART_WORD_LENGTH_8B;
h_uart1.init.stop_bits = UART_STOP_BITS_1;
h_uart1.init.parity = UART_PARITY_NONE;
h_uart1.init.mode = UART_MODE_UART;
h_uart1.init.fctl = UART_HW_FLOW_CTL_DISABLE;
h_uart1.tx_cplt_cbk = uart1_send_complete;
h_uart1.rx_cplt_cbk = uart1_recv_complete;
h_uart1.error_cbk = uart1_error;
ald_uart_init(&h_uart1);
return;
}
对于触摸屏的发送和接收直接在主函数里处理,代码如下:
ald_uart_send_by_it(&h_uart1, tx1_buf, 10);
ald_uart_recv_by_it(&h_uart1, rx1_buf, 8);
对于触摸屏的发送,无需我们处理,但是对于接收,我们必须处理,放在中断里处理,中断处理函数如下:
void uart1_recv_complete(uart_handle_t *arg)
{
if(rx1_buf[0]==0x01&&rx1_buf[1]==0x06&&rx1_buf[6]==0x88&&rx1_buf[7]==0x99)
{
FenLu=rx1_buf[5];
if(FenLu==1)
{
ald_gpio_write_pin(LED2_PORT,LED2_PIN,1);
//
//这里可以加入防呆
//
ald_gpio_write_pin(LED1_PORT,LED1_PIN,0);
}
if(FenLu==2)
{
ald_gpio_write_pin(LED2_PORT,LED2_PIN,0);
//
//这里可以加入防呆
//
ald_gpio_write_pin(LED1_PORT,LED1_PIN,1);
}
}
return;
}
4)在这套系统中,上位机电脑上的程序也是我自己编写,所以通讯速度、格式、协议我都完全掌控,所以采用串口,对串口UART2的初始化如下:
<p>void uart2_init(void)
{
gpio_init_t x;</p><p> /* Initialize tx pin */
x.mode = GPIO_MODE_OUTPUT;
x.odos = GPIO_PUSH_PULL;
x.pupd = GPIO_PUSH_UP;
x.odrv = GPIO_OUT_DRIVE_NORMAL;
x.flt = GPIO_FILTER_DISABLE;
x.type = GPIO_TYPE_TTL;
x.func = GPIO_FUNC_5;
ald_gpio_init(UART2_TX_PORT, UART2_TX_PIN, &x);</p><p> /* Initialize rx pin */
x.mode = GPIO_MODE_INPUT;
x.odos = GPIO_PUSH_PULL;
x.pupd = GPIO_PUSH_UP;
x.odrv = GPIO_OUT_DRIVE_NORMAL;
x.flt = GPIO_FILTER_DISABLE;
x.type = GPIO_TYPE_TTL;
x.func = GPIO_FUNC_5;
ald_gpio_init(UART2_RX_PORT, UART2_RX_PIN, &x);</p><p> /* clear uart_handle_t structure */
memset(&h_uart2, 0x0, sizeof(h_uart2));
/* Initialize tx_buf */
memset(tx2_buf, 0x56, 32);
/* Initialize uart */
h_uart2.perh = UART2;
h_uart2.init.baud = 115200;
h_uart2.init.word_length = UART_WORD_LENGTH_8B;
h_uart2.init.stop_bits = UART_STOP_BITS_1;
h_uart2.init.parity = UART_PARITY_NONE;
h_uart2.init.mode = UART_MODE_UART;
h_uart2.init.fctl = UART_HW_FLOW_CTL_DISABLE;
h_uart2.tx_cplt_cbk = uart2_send_complete;
h_uart2.rx_cplt_cbk = uart2_recv_complete;
h_uart2.error_cbk = uart2_error;
ald_uart_init(&h_uart2);
return;
}
</p><p> </p>
对上位机的串口发送和接收也直接在主函数里处理即可,由于协议是我自己编写,发送、接收格式和触摸屏发送、接收格式一致,这样就方便许多,代码如下:
ald_uart_send_by_it(&h_uart2, tx1_buf, 10);
ald_uart_recv_by_it(&h_uart2, rx2_buf, 8);
对于上位机电脑的发送,无需我们处理,但是对于接收,我们必须处理,放在中断里处理,中断处理函数如下:
void uart2_recv_complete(uart_handle_t *arg)
{
if(rx2_buf[0]==0x01&&rx2_buf[1]==0x06&&rx2_buf[6]==0x88&&rx2_buf[7]==0x99)
{
FenLu=rx2_buf[5];
if(FenLu==1)
{
ald_gpio_write_pin(LED2_PORT,LED2_PIN,1);
//
//这里可以加入防呆
//
ald_gpio_write_pin(LED1_PORT,LED1_PIN,0);
}
if(FenLu==2)
{
ald_gpio_write_pin(LED2_PORT,LED2_PIN,0);
//
//这里可以加入防呆
//
ald_gpio_write_pin(LED1_PORT,LED1_PIN,1);
}
}
return;
}
至此,本套系统的全部代码都编写完成了,烧录到板子上进行试验,可以完美运行。
6.上位机电脑程序的编写,主要功能就是:
A.在上位机上显示A路和B路状态;
B.在上位机上显示采集的数据;
C.在上位机上进行A路和B路切换;
配合下位机可以完美运行:
五.演示视频:
1.使用按钮控制A/B路切换的视频地址;
https://www.bilibili.com/video/BV1Qt4y1C7nz
2.使用触摸屏控制A/B路切换的视频地址;
https://www.bilibili.com/video/BV1Fi4y1t7rk
3.使用上位机控制A/B路切换的视频地址;
https://www.bilibili.com/video/BV1Qp4y1Q7tE
4.设计的配电柜A/B路切换的综合演示视频地址:
https://www.bilibili.com/video/BV1rD4y1Q7Yd
六.源代码地址:
1.触摸屏源代码:按照以上步骤配置的触摸屏源代码只有一个文件:《触摸屏:RT-Thread创新大赛.HMI》,
触摸屏:RT-Thread创新大赛.rar
(234.43 KB)
2.东软载波RT-Thread源代码:按照以上步骤配置的源代码只有一个文件《main.c》,
main.rar
(2.25 KB)
其余涉及东软载波的代码直接直接从官网(http://www.essemi.com)下载即可;涉及RT-Thread的代码直接从RT-Thread官网(https://www.rt-thread.org)下载即可。
3.上位机使用VB写的,只有一个文件《上位机:RT-Thread创新大赛.frm》
上位机:RT-Thread创新大赛.rar
(2.28 KB)
七.总结:
1.东软载波板子功能很多,这个演示系统只用到:时钟、中断、ADC、GPIO、UART等几个简单的功能;
2. RT-Thread是一个功能强大的系统,这个演示系统只用到:RT-Thread内核、时钟、中断、延时等几个简单功能,RT-Thread很多高级功能和组件都没有用的,后续持续学习,争取将其完整版的高级功能和组件都尝试一把。
3.特别说明:RT-Thread的文档中心绝对值得所有程序员去学习的地方,是一个快速入手的最佳地方。
最后一个最重要的是这篇文案。
yljon:基于RT-Thread与东软载波的上位机下位机开发.rar
(3.84 MB)
|