本程序在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);
}
|
|