STM32G0开发笔记-Platformio+libopencm3-FreeRTOS和FreeModbus库使用


title: STM32G0开发笔记-Platformio+libopencm3-FreeRTOS和FreeModbus库使用
tags:

  • STM32
  • MCU
  • STM32G070
  • libopencm3
  • MonkeyPi
  • FreeRTOS
  • Modbus
    categories:
    • STM32
      date: 2022-9-11 19:52:05

[原文:makerinchina.cn]

使用Platformio平台的libopencm3开发框架来开发STM32G0,以下为FreeRTOS和FreeModbus库使用。

1 新建项目
  • 建立freertos_modbus项目

在PIO的Home页面新建项目,项目名称freertos_modbus,选择开发板为 MonkeyPi_STM32_G070RB,开发框架选择libopencm3;

  • 项目建立完成后在src目录下新建main.c主程序文件;
  • 修改下载和调试方式,这里开发板使用的是DAPLink仿真器,因此修改platformio.ini文件如下:
upload_protocol = cmsis-dap
debug_tool = cmsis-dap
2 编写程序

直接在之前的FreeRTOS工程上进行添加;

2.1 添加 freeModbus 库

从git仓库下载源码: https://github.com/cwalter-at/freemodbus

将下载的源码中的mobus文件夹放置到工程的lib目录下,然后在modbus目录新建library.json文件,内容如下:

{
    "name": "FreeModbus",
    "version": "master",
    "repository":{
        "type":"git",
        "url":"https://github.com/cwalter-at/freemodbus"
    },
    "build": {
        "flags": [
            "-Iascii",
            "-Ifunctions",
            "-Iinclude",
            "-Irtu",
            "-Itcp"
        ],
        "srcFilter": [
            "+<*>"
        ]
    }
}

然后从FreeModbus源码中的 demo\BARE\port中复制文件到工程的src\modbus_port文件夹下,最后的文件夹结构如下:

在这里插入图片描述

2.2 移植
  • portevent:
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
#include "FreeRTOS.h"
#include "task.h"

/* ----------------------- Variables ----------------------------------------*/
static eMBEventType eQueuedEvent;
static BOOL     xEventInQueue;
static uint32_t modbus_last_active_time = 0;

uint32_t get_modbus_last_active_time(void)
{
	return modbus_last_active_time;
}

/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortEventInit( void )
{
    xEventInQueue = FALSE;
    return TRUE;
}

BOOL
xMBPortEventPost( eMBEventType eEvent )
{
    xEventInQueue = TRUE;
    eQueuedEvent = eEvent;

	if (eEvent == EV_EXECUTE) {
		modbus_last_active_time = xTaskGetTickCount();
	}
    return TRUE;
}

BOOL
xMBPortEventGet( eMBEventType * eEvent )
{
    BOOL            xEventHappened = FALSE;

    if( xEventInQueue )
    {
        *eEvent = eQueuedEvent;
        xEventInQueue = FALSE;
        xEventHappened = TRUE;
    }
    return xEventHappened;
}
  • portserial

这里使用RS485,因此需要对RS485使能端口进行配置,其他为串口的配置,然后在发送和接收中断时候调用modbus相关接口进行处理:

#include "port.h"

#include "FreeRTOS.h"
#include "queue.h"

#include <libopencm3/cm3/nvic.h>
#include <libopencm3/stm32/usart.h>
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"

/* ----------------------- static functions ---------------------------------*/

xQueueHandle uart_queue;

#define RS485_1_CLOCK		RCC_GPIOB
#define RS485_1_EN_PORT		GPIOB
#define RS485_1_EN_PIN		GPIO8

static void rs485_delay(int n)
{
    while (--n) {
        __asm__ volatile ("nop");
    }
}

static inline void rs485_1_rx_mode(void)
{
    gpio_clear(RS485_1_EN_PORT, RS485_1_EN_PIN);
}

static inline void rs485_1_tx_mode(void)
{
    gpio_set(RS485_1_EN_PORT, RS485_1_EN_PIN);
}

static inline void rs485_gpio_init(void)
{
	rcc_periph_clock_enable(RS485_1_CLOCK);
	gpio_mode_setup(RS485_1_EN_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, RS485_1_EN_PIN);

	rs485_1_rx_mode();
}

/* ----------------------- Start implementation -----------------------------*/
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
    /* If xRXEnable enable serial receive interrupts. If xTxENable enable
     * transmitter empty interrupts.
     */
    if (xRxEnable) {
        rs485_delay(10000);
        rs485_1_rx_mode();
        rs485_delay(10000);
		usart_enable_rx_interrupt(USART1);
    }
    else {
		usart_disable_rx_interrupt(USART1);
    }
    
    if (xTxEnable) {
        rs485_delay(10000);
        rs485_1_tx_mode();
        rs485_delay(10000);
		usart_enable_tx_interrupt(USART1);
    }
    else {
		usart_disable_tx_interrupt(USART1);

    }
}

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
	nvic_enable_irq(NVIC_USART1_IRQ);

	rcc_periph_clock_enable(RCC_GPIOB);
	gpio_mode_setup(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO6 | GPIO7);
	gpio_set_af(GPIOB, GPIO_AF0, GPIO6 | GPIO7);

	rcc_periph_clock_enable(RCC_USART1);

	/* Set up USART/UART parameters using the libopencm3 helper functions */
	usart_set_baudrate(USART1, ulBaudRate);
	usart_set_databits(USART1, ucDataBits);
	usart_set_stopbits(USART1, USART_STOPBITS_1);
	usart_set_mode(USART1, USART_MODE_TX_RX);

    switch (eParity) {
        case MB_PAR_ODD:
			usart_set_parity(USART1, USART_PARITY_ODD);
            break;
        case MB_PAR_EVEN:
			usart_set_parity(USART1, USART_PARITY_EVEN);
            break;
        default:
			usart_set_parity(USART1, USART_PARITY_NONE);
            break;
    }

	usart_set_flow_control(USART1, USART_FLOWCONTROL_NONE);

	usart_enable(USART1);

    rs485_gpio_init();

    return TRUE;
}

BOOL
xMBPortSerialPutByte( CHAR ucByte )
{

    usart_send_blocking(USART1, (uint16_t) ucByte);    
    
    return TRUE;
}

BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
	*pucByte = usart_recv(USART1);

    return TRUE;
}


uint32_t uart1_isr, uart1_icr;

void usart1_isr(void)
{

	/* Check if we were called because of RXNE. */
	if (((USART_CR1(USART1) & USART_CR1_RXNEIE) != 0) &&
	    ((USART_ISR(USART1) & USART_ISR_RXNE) != 0)) {

		/* Retrieve the data from the peripheral. */
        // usart_recv(USART1);

		pxMBFrameCBByteReceived();

	}


	/* Check if we were called because of TXE. */
	if (((USART_CR1(USART1) & USART_CR1_TXEIE) != 0) &&
	    ((USART_ISR(USART1) & USART_ISR_TXE) != 0)) {

		/* Put data into the transmit register. */
		//usart_send(USART1, data);

		pxMBFrameCBTransmitterEmpty();

	}

}
  • porttimer
#include "port.h"

#include <libopencm3/cm3/nvic.h>
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/timer.h>

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"

/* ----------------------- static functions ---------------------------------*/
static void prvvTIMERExpiredISR( void );

/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
	rcc_periph_clock_enable(RCC_TIM2);
	nvic_enable_irq(NVIC_TIM2_IRQ);
	rcc_periph_reset_pulse(RST_TIM2);

	timer_set_mode(TIM2, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP);

	timer_set_prescaler(TIM2, (rcc_apb1_frequency/ 20000));

	timer_disable_preload(TIM2);
	timer_continuous_mode(TIM2);

	timer_set_period(TIM2, usTim1Timerout50us);
	timer_enable_counter(TIM2);

	timer_enable_irq(TIM2, TIM_DIER_UIE);

    return TRUE;
}


inline void
vMBPortTimersEnable(  )
{
	timer_set_counter(TIM2, 0);
	timer_enable_counter(TIM2);
}

inline void
vMBPortTimersDisable(  )
{
	timer_disable_counter(TIM2);
}

/* Create an ISR which is called whenever the timer has expired. This function
 * must then call pxMBPortCBTimerExpired( ) to notify the protocol stack that
 * the timer has expired.
 */
static void prvvTIMERExpiredISR( void )
{
    ( void )pxMBPortCBTimerExpired(  );
}

void
vMBPortTimersDelay( USHORT usTimeOutMS )
{
    vTaskDelay(pdMS_TO_TICKS(usTimeOutMS));
}

void tim2_isr(void)
{
	if (timer_get_flag(TIM2, TIM_SR_UIF)) {

		/* Clear compare interrupt flag. */
		timer_clear_flag(TIM2, TIM_SR_UIF);

        prvvTIMERExpiredISR();

	}
}

开启定时器和中断,用于modbus时序控制;

2.3 使用

在src目录新建 modbus_cb.h 和 modbus_cb.c 两个文件,实现寄存器、线圈的读写回调:

/// CMD4
eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs );

/// CMD6、3、16
eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode );

/// CMD1、5、15
eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode );

/// CMD4
eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete );

基本的实现示例如下:

#include "modbus_cb.h"
#include "stdbool.h"

extern log(const char* fmt, ...);

// 输入寄存器
#define REG_INPUT_SIZE  32
uint16_t REG_INPUT_BUF[REG_INPUT_SIZE];

// 保持寄存器
#define REG_HOLD_SIZE   32
uint16_t REG_HOLD_BUF[REG_HOLD_SIZE];

// 线圈寄存器
#define REG_COILS_SIZE 16
uint8_t REG_COILS_BUF[REG_COILS_SIZE];

// 离散量
#define REG_DISC_SIZE  8
uint8_t REG_DISC_BUF[REG_DISC_SIZE];

/// CMD4
eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
    USHORT usRegIndex = usAddress - 1; 

	// 非法检测
	if((usRegIndex + usNRegs) > REG_INPUT_SIZE)
	{
		return MB_ENOREG;
	}

    log(" CMD4, 寄存器输入.");

	// 填充数据
    REG_INPUT_BUF[0] = 0x01;
	REG_INPUT_BUF[1] = 0x02;

    // 循环读取
	while ( usNRegs > 0 ) {
		*pucRegBuffer++ = ( unsigned char )( REG_INPUT_BUF[usRegIndex] >> 8 );
		*pucRegBuffer++ = ( unsigned char )( REG_INPUT_BUF[usRegIndex] & 0xFF );
		usRegIndex++;
		usNRegs--;
	}

	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;
	}

    log(" CMD3,6,16, 保持寄存器读写.");
    
	// 写寄存器
	if (eMode == MB_REG_WRITE) {
		while ( usNRegs > 0 ) {
			uint16_t value;

			value = (pucRegBuffer[0] << 8) | pucRegBuffer[1];

			log("  写寄存器值:%d", value);

			pucRegBuffer += 2;
			usRegIndex++;
			usNRegs--;

        }

    }
    // 读寄存器
	else {

		log("  读寄存器.");
		
		REG_HOLD_BUF[0] = 0x32;
		REG_HOLD_BUF[1] = 0x33;

        while ( usNRegs > 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;
	USHORT usCoilGroups = ((usNCoils - 1) / 8 + 1);
	UCHAR  ucStatus     = 0;
	UCHAR  ucBits       = 0;
	UCHAR  ucDisp       = 0;

	// 非法检测
	if ((usRegIndex + usNCoils) > REG_COILS_SIZE) {
		return MB_ENOREG;
	}

    log("  CMD1,5,15, 线圈读写.");

	// 写线圈
	if (eMode == MB_REG_WRITE) {

		while (usCoilGroups--) {

			ucStatus = *pucRegBuffer++;
			ucBits   = 8;

			while((usNCoils) != 0 && (ucBits) != 0) {
				bool flag = ucStatus & 0x01;

				switch (usRegIndex) {
					
                    case 0:
						log(" 线圈0 : %d", flag);//
                    break;

					case 1:
						log(" 线圈1 : %d", flag);
					break;

					default:

					break;

				}

				usRegIndex++;
				ucStatus >>= 1;
				usNCoils--;
				ucBits--;
			}

		}
	}
    // 读线圈
	else {
		
		REG_COILS_BUF[0]  = 1;
		REG_COILS_BUF[1]  = 0;

		while (usCoilGroups--) {
			ucDisp = 0;
			ucBits = 8;
			ucStatus = 0;

			while((usNCoils) != 0 && (ucBits) != 0) {
				ucStatus |= (REG_COILS_BUF[usRegIndex++] << (ucDisp++));
				usNCoils--;
				ucBits--;
			}

			*pucRegBuffer++ = ucStatus;
		}
	}

	return MB_ENOERR;
}

/// CMD4
eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
    USHORT usRegIndex   = usAddress - 1;
	USHORT usCoilGroups = ((usNDiscrete - 1) / 8 + 1);
	UCHAR  ucStatus     = 0;
	UCHAR  ucBits       = 0;
	UCHAR  ucDisp       = 0;

	// 非法检测
	if ((usRegIndex + usNDiscrete) > REG_DISC_SIZE) {
		return MB_ENOREG;
	}

    log("  CMD4, 离散寄存器写入.");

	// 读离散输入
	while (usCoilGroups--) {
		ucDisp = 0;
		ucBits = 8;
		ucStatus = 0;

		while((usNDiscrete != 0) && (ucBits != 0))
		{
			switch (usRegIndex) {
			case 0:
				ucStatus = 0x10;
				break;
			}

			usRegIndex++;
			ucDisp++;
			usNDiscrete--;
			ucBits--;
		}
		*pucRegBuffer++ = ucStatus;
	}

    	return MB_ENOERR;
}

在main中创建modbus任务:

static void task_modbus_handle(void *param)
{

    eMBErrorCode    eStatus;

    log("  task modbus start.");

    eStatus = eMBInit( MB_RTU, 0x01, 0, 9600, MB_PAR_NONE );

    /* Enable the Modbus Protocol Stack. */
    eStatus = eMBEnable();

	(void)eStatus;

    for( ;; ) {
        ( void )eMBPoll();
        vTaskDelay(pdMS_TO_TICKS(10));
    }

}
3 烧写测试

将开发板连接到USB转485模块,然后使用modbus poll程序进行测试:

在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: STM32 HAL可以与FreeRTOSFreeModbus相结合使用,以实现IAR IAR作为开发环境。 FreeRTOS是一个开源的实时操作系统,可以优化处理器资源使用,提供任务调度、时间管理、IPC等功能。STM32 HAL提供与FreeRTOS的适配层,使得在STM32芯片上可以轻松地使用FreeRTOSFreeModbus是一个开源的Modbus通信协议栈,用于在主机和从机之间进行通信。通过使用HAL的串口驱动功能,可以实现STM32作为Modbus从机或主机。 将HALFreeRTOS集成到IAR开发环境中,可以按照以下步骤进行: 1. 创建一个新的项目,并包括HALFreeRTOS的源码文件。可以从ST官网下载最新版本的HALFreeRTOS。 2. 配置IAR工程,包括编译器选项、链接器脚本等。确保HALFreeRTOS的路径正确,并添加需要的头文件和文件。 3. 在main函数中初始化HALFreeRTOS。HAL提供了相应的初始化函数和配置选项,可以根据具体的需求进行设置。在FreeRTOS中,可以创建任务、定时器、队列等。 4. 在任务中添加FreeModbus的代码。根据需要,可以实现STM32作为Modbus从机或主机的功能。在从机模式下,可以使用HAL中的串口驱动来接收和发送Modbus数据。 5. 编译和下载代码到STM32芯片。使用IAR进行编译和链接,并通过JTAG/SWD调试器将代码下载到芯片上。 通过以上步骤,即可实现STM32 HALFreeRTOSFreeModbus的结合使用,从而在IAR开发环境中实现相应的功能。 ### 回答2: STM32 HAL可以与FreeRTOSFreeModbus(从机和主机)一起使用开发IAR嵌入式系统。 首先,STM32 HAL开发者提供了许多硬件抽象层函数,可以轻松控制STM32系列微控制器的各种外设和功能。它简化了对硬件的操作,提高了开发效率。 FreeRTOS是一个流行的实时操作系统,可用于嵌入式系统的并发和调度管理。通过与STM32 HAL的配合使用开发者可以在STM32微控制器上运行多个任务,并使用FreeRTOS提供的任务管理功能来调度和控制任务的执行。 FreeModbus是一种用于Modbus通信协议的开源实现。它提供了主机和从机两种模式,可以在STM32微控制器上实现Modbus通信。通过STM32 HALFreeModbus的集成,开发者可以使用STM32的串行通信外设来实现Modbus通信,并利用FreeModbus的函数来处理Modbus消息的接收和发送。 IAR是一种广泛使用的集成开发环境(IDE),用于开发嵌入式系统的软件。通过在IAR中配置STM32 HALFreeRTOSFreeModbus开发者可以将它们整合在一起,并通过IAR的编译器和调试器来构建和调试嵌入式应用程序。 综上所述,开发者可以使用STM32 HAL来实现FreeRTOSFreeModbus(从机和主机)功能,通过IAR进行开发。这样的设计方案可以提高开发效率和可靠性,使得在STM32微控制器上开发嵌入式系统变得更加容易和高效。 ### 回答3: STM32 HAL可以与FreeRTOSFreeModbus一起使用来在IAR集成开发环境中实现主机和从机的通信。 首先,需要在IAR环境中配置STM32 HAL,并根据需要选择所需的外设和功能。然后,导入FreeRTOSFreeModbus,并将其配置为HAL的一部分。这可以通过在IAR中设置包含路径和链接来实现。 对于FreeRTOS的实现,首先需要配置任务和中断管理器。可以使用HAL提供的任务和中断API来创建、挂起和恢复任务,并设置任务优先级。使用HAL提供的定时器或计数器来实现任务调度。 对于FreeModbus的实现,需要配置串口或其他通信接口以与主机进行通信。可以使用HAL提供的串口或SPI接口功能来配置通信接口。然后,可以使用FreeModbus的函数来实现Modbus协议的从机或主机功能。这些函数包括读写寄存器、处理请求和响应等。 在主机端,可以使用HAL提供的定时器或计数器来实现Modbus主机的发送和定时功能。在从机端,可以使用HAL提供的中断或轮询功能来处理Modbus从机的请求和响应。同时,还需要实现处理从机地址和功能码的逻辑。 最后,可以在IAR中编译、调试和烧录代码。使用HALFreeRTOSFreeModbus的API来编写主机和从机的应用程序代码。在应用程序中,可以实现与其他设备的通信,并处理数据传输和处理的逻辑。 通过使用STM32 HALFreeRTOSFreeModbus,可以方便地在IAR环境中实现主机和从机的通信,并实现Modbus协议的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值