【基于STM32F1与RT-Thread的老旧电表物联改造工作——下位机端】

该方法基于单位某园区的老旧电表物联改造工作项目,作者主要负责下位机端的选型制作与编写代码、以及和服务器端工程师进行对接,并对服务器编程进行了自学。
  
开发环境
  选用RT-Thread官方idea:RT-Thread Studio,并配合使用STM32官方开发环境STM32CUBEMX配置引脚,初学者可以通过该链接进行配置
RT-Thread Studio联合STM32CubeMX进行开发
  
系统流程
在这里插入图片描述
  该项目中,下位机使用json格式数据,定时将485菊花链连接的多个电表数据发送到服务器。STM32主要使用了串口1和串口3来进行通讯,串口1将数据转换成485信号,和电表进行指令的发送和数据的读取;串口3对串口1接受到的电表数据进行json格式打包,通过4G模块发送给服务器云端。

软件流程
在这里插入图片描述
测试代码
相关变量的定义

unsigned char usart_tx_data[89];	 //4G模块发送数组
unsigned char usart_485_send[8];     //485发送数组
uint8_t power_sum[20]={
    
    0};			 //电量存储数组	
uint8_t test_data[100]={
    
    0};

rt_mutex_t dynamic_mutex = RT_NULL;

char not_connect_4G=0;          //连接4G未成功次数
unsigned char receive_ok;		//统计电表接受次数

串口1接收数据回调函数

static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{
    
    
    /* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */
    rt_sem_release(&rx_sem1);

    return RT_EOK;
}

串口1的相关线程

static void serial_thread_entry(void *parameter)
{
    
    
    char ch;
    char Con485_i = 0;
    while (1)
    {
    
    
        /* 从串口读取一个字节的数据,没有读取到则等待接收信号量 */
        while (rt_device_read(serial1, -1, &ch, 1) != 1)
        {
    
    
            /* 阻塞等待接收信号量,等到信号量后再次读取数据 */
            rt_sem_take(&rx_sem1, RT_WAITING_FOREVER);
        }

        rt_device_write(serial1, 0, &ch, 1);
        //接收到了电表发来的数据

        receive_ok++;
        if(receive_ok == 4 )//接收到了四个电表的数据
        {
    
    
            Connect_4G();
            //清空、置FF操作
            receive_ok = 0;
            power_sum[4*Con485_i-1]=0xff;
            power_sum[4*Con485_i-4]=0xff;
            power_sum[4*Con485_i-2]=0xff;
            power_sum[4*Con485_i-3]=0xff;
            Con485_i = 0;
        }
        else
        {
    
    
            power_sum[4*Con485_i++]=ch;
        }
    }
}

串口1的初始化函数并导出到 msh 命令列表中(从命令行启动)

int uart_sample(void)
{
    
    
    rt_err_t ret = RT_EOK;
    char str1[] = "Uart1:hello RT-Thread!\r\n";


    /* 查找系统中的串口设备 */
    serial1 = rt_device_find(SAMPLE_UART_NAME1);
    if (!serial1)
    {
    
    
        rt_kprintf("find %s failed!\n", SAMPLE_UART_NAME1);
        return RT_ERROR;
    }

    /* 初始化信号量 */
    rt_sem_init(&rx_sem1, "rx_sem", 0, RT_IPC_FLAG_FIFO);
    /* 以中断接收及轮询发送模式打开串口设备 */
    rt_device_open(serial1, RT_DEVICE_FLAG_INT_RX);
    /* 设置接收回调函数 */
    rt_device_set_rx_indicate(serial1, uart_input);
    /* 发送字符串 */
    rt_device_write(serial1, 0, str1, (sizeof(str1) - 1));

    /* 创建 serial 线程 */
    rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 25, 10);
    /* 创建成功则启动线程 */
    if (thread != RT_NULL)
    {
    
    
        rt_thread_startup(thread);
    }
    else
    {
    
    
        ret = RT_ERROR;
    }

    return ret;
}

/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(uart_sample, uart device sample);

串口3接收数据回调函数

static rt_err_t uart_rx_ind(rt_device_t dev, rt_size_t size)
{
    
    
    /* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */
    if (size > 0)
    {
    
    
        rt_sem_release(&rx_sem);
    }
    return RT_EOK;
}

串口3的数据解析线程与单个字符处理函数(服务器发下来的json格式数据较多,因此以该方式接受)

static char uart_sample_get_char(void)
{
    
    
    char ch;

    while (rt_device_read(serial, 0, &ch, 1) == 0)
    {
    
    
        rt_sem_control(&rx_sem, RT_IPC_CMD_RESET, RT_NULL);
        rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
    }
    return ch;
}

/* 数据解析线程 */
static void data_parsing(void)
{
    
    
    char ch;
    char data[ONE_DATA_MAXLEN];
    static char i = 0;

    while (1)
    {
    
    
        ch = uart_sample_get_char();
        rt_device_write(serial, 0, &ch, 1);
        if(ch == DATA_CMD_END)
        {
    
    
            test_data[i++] = '\0';
            rt_kprintf("data=%s\r\n",test_data);

            //遍历判断新接受到的200个字节内是否有0x6E和0x63的发送成功
            for(int sent_j=0;sent_j<i;sent_j++)
            {
    
    
                rt_kprintf("%c",test_data[sent_j]);
            }
            rt_kprintf("\r\n");
            for(int sent_j=0;sent_j<98;sent_j++)
            {
    
    
                if(test_data[sent_j]==0x6E&&test_data[sent_j+1]==0x63)
                {
    
    
                    rt_kprintf("####receive_ok=1####\r\n  okkosent_j:%d\r\n",sent_j);
                    /* 线程 获取到互斥量后,将4G未连接的次数清零 */
                    rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER);
                    not_connect_4G = 0;
                    rt_mutex_release(dynamic_mutex);
                }
            }

            //处理完上次接受的一组数据,继续while循环接受
            continue;
        }
        i = (i >= ONE_DATA_MAXLEN-1) ? ONE_DATA_MAXLEN-1 : i;
        test_data[i++] = ch;
    }
}

串口3的初始化函数以及导出到msh命令列表中

int uart_data_sample(void)
{
    
    
    rt_err_t ret = RT_EOK;
    char uart_name[] = SAMPLE_UART_NAME;
    char str3[] = "Uart3:hello RT-Thread!\r\n";

    /* 查找系统中的串口设备 */
    serial = rt_device_find(uart_name);
    if (!serial)
    {
    
    
        rt_kprintf("find %s failed!\n", uart_name);
        return RT_ERROR;
    }

    /* 初始化信号量 */
    rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
    /* 以中断接收及轮询发送模式打开串口设备 */
    rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
    /* 设置接收回调函数 */
    rt_device_set_rx_indicate(serial, uart_rx_ind);
    /* 发送字符串 */
    rt_device_write(serial, 0, str3, (sizeof(str3) - 1));

    /* 创建 serial 线程 */
    rt_thread_t thread = rt_thread_create("serial", (void (*)(void *parameter))data_parsing, RT_NULL, 1024, 25, 10);
    /* 创建成功则启动线程 */
    if (thread != RT_NULL)
    {
    
    
        rt_thread_startup(thread);
    }
    else
    {
    
    
        ret = RT_ERROR;
    }

    return ret;
}

/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(uart_data_sample, uart device sample);

串口1通过485给电表发指令,以及通过串口3给4G模块发指令(需要进行字符串与ASCII码的转换)

//读取一个485电表设备的函数
//参数Con485_i,表示读取第几个电表
//返回值表示是否读取成功,1表示成功,0表示读取失败
char Connect_485(char Con485_i)
{
    
    

    usart_485_send[0]=Con485_i+1;   //电表ID
    switch(Con485_i)
    {
    
    
        case 0:usart_485_send[6]=0x54;
                    usart_485_send[7]=0x0d;
                    break;
        case 1:usart_485_send[6]=0x54;
                    usart_485_send[7]=0x3e;
                    break;
        case 2:usart_485_send[6]=0x55;
                    usart_485_send[7]=0xef;
                    break;
        case 3:usart_485_send[6]=0x54;
                    usart_485_send[7]=0x58;
                    break;
        case 4:usart_485_send[6]=0x55;
                    usart_485_send[7]=0x89;
                    break;
    }

    /* 发送给485端 */
    int send_len = rt_device_write(serial1, 0, usart_485_send, (sizeof(usart_485_send)));
    if (send_len != sizeof(usart_485_send))
    {
    
    
        rt_kprintf("send data failed\r\n");
        return -RT_ERROR;
    }

}

char Connect_4G(void)
{
    
    
    //字符串与ASCII码的数据转换,结果存在usart_tx_data中,这里不再书写
    
    /* 发送给服务器端 */
    send_len = rt_device_write(serial, 0, usart_tx_data, (sizeof(usart_tx_data)));
    if (send_len != sizeof(usart_tx_data))
    {
    
    
        rt_kprintf("send data failed\r\n");
        return -RT_ERROR;
    }
    return 0;
}

main函数,负责5s周期发送电表指令以及错误判断进行重启

int main(void)
{
    
    
    char not_connect_485=0;         //连接485未成功次数
    char uart_i=0;                  //所读电表的ID序列
    char not_con485_i[5]={
    
    0};
    int i = 0;                      //用于循环计数
    unsigned char test_data[8] = {
    
    0x02,0x03,0x00,0x1d,0x00,0x02,0x54,0x3e};

    LOG_D("Hello RT-Thread!");
    rt_pin_mode(15, PIN_MODE_OUTPUT);
    /* 创建一个动态互斥量 */
    dynamic_mutex = rt_mutex_create("dmutex", RT_IPC_FLAG_PRIO);
    if (dynamic_mutex == RT_NULL)
    {
    
    
        rt_kprintf("create dynamic mutex failed.\n");
        return -1;
    }
    //不用命令行,以直接初始化的方式初始化串口1和3
    //uart_sample();
    //uart_data_sample();
    while (1)
    {
    
    
        //延时5s
        rt_thread_mdelay(4000);
        //小于等于3时读第一个电表数据,等于4时发送给4G
        if(uart_i<=3)
        {
    
    
            not_con485_i[uart_i] = Connect_485(uart_i);
        }
        
        //所读电表id++,共0-4号五个电表
        uart_i=(uart_i+1)%5;
        
        rt_thread_mdelay(1000);
        rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER);
        not_connect_4G++;
        rt_mutex_release(dynamic_mutex);
        if(not_connect_485==3||not_connect_4G>=3)
        {
    
    
            rt_kprintf("$$$$$$restart$$$$$$\r\n");
            //4G模块重启,加在一个cube里面定义引脚
            HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);
            rt_thread_mdelay(800);
            HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET);
            //stm32模块重启
            __set_FAULTMASK(1);
            NVIC_SystemReset();
        }

    }

    return RT_EOK;
}

猜你喜欢

转载自blog.csdn.net/Sky777wdnmd/article/details/123555368
今日推荐