文章
博客 网店

 ATMEGA48上实现的RTU模式MODBUS从机程序


本程序在ATMEGA48单片机上实现的MODBUS从机功能,
支持MODEBUS两个命令
1.读多个寄存器(0x03)
2.写多个寄存器(0x10)
编译后可看出占用空间仅有1576字节,所以ATMEGA48还是可以接受的.下面是源代码:

UART驱动部分

//uart.h
#ifndef UART_H
#define UART_H

#define UART_BUF_SIZE 32      //UART数据缓冲区长度定义 

//状态标记
#define UART_IDLE  0  //空闲状态
#define UART_SEND   1  //正在发送数据帧
#define UART_RECV  2  //接收到数据帧

void uart_init(void);
void uart_send(uint8_t *buf,uint8_t len);
uint8_t uart_recv(uint8_t *buf);
uint8_t uart_read_state(void);

#endif

/****************************************
  ATMEGA48上实现的MODBUS RTU模式从机程序 
  文件名:uart.c
  编译:WinAVR-20070525

  硬件:ATMEGA48  时钟:7372800 Hz

  此程序在硬件上调试通过!    
  
  芯艺设计室 2004-2008  版权所有 
  转载请保留本注释在内的全部内容
  WEB: http://www.chipart.cn
  Email: changfutong@sina.com
****************************************/
#include<avr/io.h>
#include<avr/interrupt.h>

#include"queue.h"
#include"uart.h"
#include"modbus.h"

#define SEL_RX_EN PORTD&=~_BV(PD2) //RS485接收状态选择
#define SEL_TX_EN  PORTD|=_BV(PD2)  //RS485发送状态选择

HQUEUE g_RxQueue; //接收数据队列
HQUEUE g_TxQueue; //发送数据队列
uint8_t g_RxBuffer[UART_BUF_SIZE];  //接收缓冲
uint8_t g_TxBuffer[UART_BUF_SIZE];  //发送缓冲

uint8_t volatile  g_UartState;   //UART工作状态标记


//modbus限时计数器打开
void mbTimerEnable(void)
{
  //使用定时器寄存器2
  TCCR2A=0;  
  TCCR2B=0;
  TCNT2=0;
  TCCR2B=_BV(CS22)|_BV(CS20);//128分频,CPU时钟7.3728MHz时计数256溢出的时间约4.5ms
  TIMSK2=_BV(TOIE2);
}
void mbTimerDisable(void)
{
  TCCR2B=0;
  TIMSK2=0;
}

//定时器中断 ,表示帧接收完成
ISR(TIMER2_OVF_vect)
{
  mbTimerDisable();  
  g_UartState=UART_RECV;
  mbResponse();
}

void uart_init(void)
{
  //uart硬件初始化 
  UBRR0H=0;
  UBRR0L=47; //波特率:9600
  UCSR0B=_BV(RXEN0)|_BV(TXEN0)|_BV(TXCIE0)|_BV(RXCIE0);
  
  //rs485 传输方向控制
  DDRD|=_BV(PD2);
  SEL_RX_EN;
  
  //发送/接收队列初始化
  QueueCreate(&g_RxQueue,g_RxBuffer,UART_BUF_SIZE);
  QueueCreate(&g_TxQueue,g_TxBuffer,UART_BUF_SIZE);
  
  //超时定时器禁止
  mbTimerDisable();
  g_UartState=UART_IDLE;
}

//接收一字节中断
ISR(USART_RX_vect)
{
  uint8_t recv=UDR0;
  if(g_UartState!=UART_IDLE)//如果UART工作状态为忙,则忽略
    return ;
  QueueInput(&g_RxQueue,recv);
  mbTimerEnable();
}

//发送寄存器空中断
ISR(USART_UDRE_vect)
{
  if(QueueGetSize(&g_TxQueue)>0)//如果发送队列中还有数据
    UDR0=QueueOutput(&g_TxQueue);  
  else
    UCSR0B&=~_BV(UDRIE0);
}

//发送完成中断
ISR(USART_TX_vect)
{
  SEL_RX_EN;          //释放总线
  g_UartState=UART_IDLE;  //状态返回到空闲
}

//发送指定长度的数据
void uart_send(uint8_t *buf,uint8_t len)
{
  uint8_t i;
  QueueClear(&g_TxQueue);
  for(i=0;i<len;i++)
    QueueInput(&g_TxQueue,buf[i]);
  
  SEL_TX_EN;
  g_UartState=UART_SEND;
  UDR0=QueueOutput(&g_TxQueue);
  UCSR0B|=_BV(UDRIE0);    
}

//读数据帧,返回读取长度
uint8_t uart_recv(uint8_t *buf)
{
  uint8_t i;
  
  for(i=0;i<UART_BUF_SIZE;i++)
  {
    if(QueueGetSize(&g_RxQueue)==0)
      break;
    buf[i]=QueueOutput(&g_RxQueue);
  }  
  QueueClear(&g_RxQueue);
  g_UartState=UART_IDLE;
  return i;
}

//读当前UART工作状态
uint8_t uart_read_state(void)
{
  return g_UartState;
}


//queue.h

#ifndef QUEUE_H
#define QUEUE_H

//队列数据结构
typedef struct QUEUE_S
{
  uint8_t in_index;//入队地址
  uint8_t out_index;//出队地址
  uint8_t buf_size; //缓冲区长度
  uint8_t *pBuffer;//缓冲
  volatile uint8_t  data_count; //队列内数据个数
  uint8_t error;
}HQUEUE,*PHQUEUE;

void QueueInput(PHQUEUE Q,uint8_t dat);
uint8_t QueueOutput(PHQUEUE Q);
uint8_t QueueGetSize(PHQUEUE Q);
void QueueClear(PHQUEUE Q);
void QueueCreate(PHQUEUE Q,uint8_t *buffer,uint8_t buf_size);

#endif


/****************************************
  ATMEGA48上实现的MODBUS RTU模式从机程序 
  文件名:queue.c
  编译:WinAVR-20070525

  硬件:ATMEGA48  时钟:7372800 Hz

  此程序在硬件上调试通过!    
  
  芯艺设计室 2004-2008  版权所有 
  转载请保留本注释在内的全部内容
  WEB: http://www.chipart.cn
  Email: changfutong@sina.com
****************************************/

#include<avr/io.h>

#include"queue.h"

//向队列插入一字节
void QueueInput(PHQUEUE Q,uint8_t dat)
{
  if(Q->data_count < Q->buf_size)
  {
    Q->pBuffer[Q->in_index]=dat;    //写入数据
    Q->in_index=(Q->in_index+1) % (Q->buf_size);//调整入口地址
    Q->data_count++;  //调整数据个数(此操作不可被中断)
  }
  else
  {
    if(Q->error<255)
      Q->error++;
  }


//从队列读出一字节
uint8_t QueueOutput(PHQUEUE Q)
{
  uint8_t Ret=0;
  
  if(Q->data_count > 0)
  {
    Ret=Q->pBuffer[Q->out_index];  //读数据
    

    Q->out_index=(Q->out_index+1) % (Q->buf_size);  //调整出口地址
    Q->data_count--;

  }
  return Ret;
}
//获得队列中数据个数
uint8_t QueueGetSize(PHQUEUE Q)
{
  return Q->data_count;
}

//清空队列,执行时不可被中断
void QueueClear(PHQUEUE Q)
{
  Q->in_index=0;
  Q->out_index=0;
  Q->data_count=0;
  Q->error=0;
}

//初始化一队列
void QueueCreate(PHQUEUE Q,uint8_t *buffer,uint8_t buf_size)
{
  Q->pBuffer=buffer;
  Q->buf_size=buf_size;
  QueueClear(Q);
}


modbus协议部分

//modbus.h
#ifndef MODBUS_H
#define MODBUS_H

//如果有这个定义CRC计算将使用小空间模式,此时CRC算法占用空间小但执行速度慢,否则相反
#define SMALL_CRC_MODE

//本机MODBUS地址定义
#define MY_MODBUS_ADDRESS 1

void mbResponse(void);

//以下两个函数用于在主程序中更新或读取保持寄存器
void mb_write_reg(uint8_t addr,uint16_t dat);
uint16_t mb_read_reg(uint8_t addr);

#endif


/****************************************
  ATMEGA48上实现的MODBUS RTU模式从机程序 
  文件名:modbus.c
  编译:WinAVR-20070525

  硬件:ATMEGA48  时钟:7372800 Hz

  此程序在硬件上调试通过!    
  
  芯艺设计室 2004-2008  版权所有 
  转载请保留本注释在内的全部内容
  WEB: http://www.chipart.cn
  Email: changfutong@sina.com
****************************************/

#include<avr/io.h>
#include<avr/interrupt.h>
#include<avr/pgmspace.h>
#include <util/crc16.h>

#include"uart.h"
#include"modbus.h"

#define HOLD_REG_SIZE 6

uint16_t g_HolReg[HOLD_REG_SIZE] __attribute__((section(".noinit"))); //保持寄存器
uint8_t g_mbBuf[UART_BUF_SIZE];    //通信用缓冲区

#ifdef SMALL_CRC_MODE

uint16_t mbCRC16( uint8_t * pucFrame, uint16_t usLen )
{
   uint8_t hi,lo;
   uint16_t i;
   uint16_t crc;
   crc=0xFFFF;
   for (i=0;i<usLen;i++)
   {
     crc= _crc16_update(crc, *pucFrame);
     pucFrame++;
   }
  hi=crc%256;
  lo=crc/256;
  crc=(hi<<8)|lo;
  return crc;
}

#else //BIG_CRC_MODE

static const PROGMEM uint8_t aucCRCHi[] = {
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
    0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
    0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81,
    0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
    0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
    0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
    0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
    0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
    0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
    0x40
};

static const PROGMEM uint8_t aucCRCLo[] = {
    0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4,
    0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
    0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD,
    0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
    0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7,
    0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
    0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE,
    0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
    0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2,
    0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
    0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB,
    0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
    0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91,
    0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
    0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88,
    0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
    0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80,
    0x40
};

uint16_t mbCRC16( uint8_t * pucFrame, uint16_t usLen )
{
    uint8_t           ucCRCHi = 0xFF;
    uint8_t           ucCRCLo = 0xFF;
    int             iIndex;

    while( usLen-- )
    {
        iIndex = ucCRCLo ^ *( pucFrame++ );
        ucCRCLo = ucCRCHi ^ pgm_read_byte( &aucCRCHi[iIndex] );
        ucCRCHi = pgm_read_byte( &aucCRCLo[iIndex] );;
    }
    return (((uint16_t)ucCRCLo) << 8) | ucCRCHi;
}

#endif    //CRC MODE

void mb_write_reg(uint8_t addr,uint16_t dat)
{
  uint8_t flg=SREG&0X80;
  
  cli();
  g_HolReg[addr]=dat;
  
  if(flg==0x80)
    sei();
}
uint16_t mb_read_reg(uint8_t addr)
{
  return g_HolReg[addr];
}

uint16_t BufToReg(uint8_t *buf)
{
  uint16_t ret=buf[0];
  ret<<=8;
  return ret+buf[1];
}

void RegToBuf(uint8_t *buf,uint16_t reg)
{
  buf[0]=reg>>8;
  buf[1]=reg;
}

void mbSendError(uint8_t err)
{
  uint16_t crc;
  
  g_mbBuf[1]|=0x80;
  g_mbBuf[2]=err;
  crc=mbCRC16(g_mbBuf,3);
  RegToBuf(g_mbBuf+3,crc);
  uart_send(g_mbBuf,5);  
}

//读寄存器命令处理
void mbReadReg(void)
{
  uint8_t i,len;
  uint16_t addr,size;
    
  addr=BufToReg(g_mbBuf+2);
  size=BufToReg(g_mbBuf+4);
  
  if(size<1 || size>=0x007d)
  {
    mbSendError(0x03);
    return ;
  }
  if(addr+size > HOLD_REG_SIZE)
  {
    mbSendError(0x02);
    return ;
  }
  
  g_mbBuf[2]=size*2;
  
  for(i=0;i<size;i++)
    RegToBuf(g_mbBuf+3+(i*2),g_HolReg[addr+i]);
  
  len=3+(size*2);
  addr=mbCRC16(g_mbBuf,len);
  RegToBuf(g_mbBuf+len,addr);
  len+=2;
  uart_send(g_mbBuf,len);
}

//写寄存器命令处理
void mbWriteReg(void)
{
  uint8_t i;
  uint16_t addr,size;
    
  addr=BufToReg(g_mbBuf+2);
  size=BufToReg(g_mbBuf+4);
  
  if((size<1) || (size>=0x007b) || ((size*2)!= g_mbBuf[6]))
  {
    mbSendError(0x03);
    return ;
  }
  if(addr + size > HOLD_REG_SIZE)
  {
    mbSendError(0x02);
    return ;
  }
  
  for(i=0;i<size;i++)
  {
    g_HolReg[i+addr]=BufToReg(g_mbBuf+7+(i*2));
  }
  
  addr = mbCRC16(g_mbBuf,6);
  RegToBuf(g_mbBuf+6,addr);
  uart_send(g_mbBuf,8);  
}

//命令响应处理
void mbResponse(void)
{
  uint8_t i;

  if(uart_read_state()==UART_RECV)
  {
    i=uart_recv(g_mbBuf);
    if(g_mbBuf[0]!=MY_MODBUS_ADDRESS) //地址校验
      return ;    
    if(mbCRC16(g_mbBuf,i)!=0)//CRC校验
      return ;

    if(g_mbBuf[1]== 0x03)  //读寄存器
      mbReadReg();
    else if(g_mbBuf[1]==0x10)//写寄存器
      mbWriteReg();
    else
      mbSendError(0x01);
  }
}


主程序内容

/****************************************
  ATMEGA48上实现的MODBUS RTU模式从机程序 
  文件名:main.c
  编译:WinAVR-20070525

  硬件:ATMEGA48  时钟:7372800 Hz

  此程序在硬件上调试通过!    
  
  芯艺设计室 2004-2008  版权所有 
  转载请保留本注释在内的全部内容
  WEB: http://www.chipart.cn
  Email: changfutong@sina.com
****************************************/

#include<avr/io.h>
#include<avr/interrupt.h>

#include"uart.h"
#include"modbus.h"

//主程序入口
int main(void)
{
  uart_init();
  sei();
  while(1);
}

 

芯艺工作室    蒙ICP备06005492号
Copyright© 2004-2023 ChipArt Studio All Rights Reserved