title: STM32G0开发笔记-Platformio+libopencm3-FreeRTOS和FreeModbus库使用
tags:
- STM32
- MCU
- STM32G070
- libopencm3
- MonkeyPi
- FreeRTOS
- Modbus
categories:- STM32
date: 2022-9-11 19:52:05
- STM32
[原文: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程序进行测试: