|
/*********************************************************/
#include <STC8G.H>
#define MAIN_Fosc 11059200L //定義主時(shí)鐘
/************* 功能說明 **************
請(qǐng)先別修改程序, 直接下載"08-串口1中斷收發(fā)-C語言-MODBUS協(xié)議"里的"UART1.hex"測(cè)試, 主頻選擇11.0592MHZ. 測(cè)試正常后再修改移植.
串口1按MODBUS-RTU協(xié)議通信. 本例為從機(jī)程序, 主機(jī)一般是電腦端.
本例程只支持多寄存器讀和多寄存器寫, 寄存器長(zhǎng)度為64個(gè), 別的命令用戶可以根據(jù)需要按MODBUS-RTU協(xié)議自行添加.
本例子數(shù)據(jù)使用大端模式(與C51一致), CRC16使用小端模式(與PC一致).
默認(rèn)參數(shù):
串口1設(shè)置均為 1位起始位, 8位數(shù)據(jù)位, 1位停止位, 無校驗(yàn).
串口1(P3.0 P3.1): 9600bps.
定時(shí)器0用于超時(shí)計(jì)時(shí). 串口每收到一個(gè)字節(jié)都會(huì)重置超時(shí)計(jì)數(shù), 當(dāng)串口空閑超過35bit時(shí)間時(shí)(9600bps對(duì)應(yīng)3.6ms)則接收完成.
用戶修改波特率時(shí)注意要修改這個(gè)超時(shí)時(shí)間.
本例程只是一個(gè)應(yīng)用例子, 科普MODBUS-RTU協(xié)議并不在本例子職責(zé)范圍, 用戶可以上網(wǎng)搜索相關(guān)協(xié)議文本參考.
本例定義了64個(gè)寄存器, 訪問地址為0x1000~0x103f.
命令例子:
寫入4個(gè)寄存器(8個(gè)字節(jié)):
10 10 1000 0004 08 1234 5678 90AB CDEF 4930
返回:
10 10 10 00 00 04 4B C6
讀出4個(gè)寄存器:
10 03 1000 0004 4388
返回:
10 03 08 12 34 56 78 90 AB CD EF 3D D5
命令錯(cuò)誤返回信息(自定義):
0x90: 功能碼錯(cuò)誤. 收到了不支持的功能碼.
0x91: 命令長(zhǎng)度錯(cuò)誤.
0x92: 寫入或讀出寄存器個(gè)數(shù)或字節(jié)數(shù)錯(cuò)誤.
0x93: 寄存器地址錯(cuò)誤.
注意: 收到廣播地址0x00時(shí)要處理信息, 但不返回應(yīng)答.
******************************************/
/************* 本地常量聲明 **************/
#define RX1_Length 128 /* 接收緩沖長(zhǎng)度 */
#define TX1_Length 128 /* 發(fā)送緩沖長(zhǎng)度 */
typedef unsigned char u8;
typedef unsigned int u16;
typedef unsigned long u32;
/************* 本地變量聲明 **************/
u8 xdata RX1_Buffer[RX1_Length]; //接收緩沖
u8 xdata TX1_Buffer[TX1_Length]; //發(fā)送緩沖
u8 RX1_cnt; //接收字節(jié)計(jì)數(shù).
u8 TX1_cnt; //發(fā)送字節(jié)計(jì)數(shù)
u8 TX1_number; //要發(fā)送的字節(jié)數(shù)
u8 RX1_TimeOut; //接收超時(shí)計(jì)時(shí)器
bit B_RX1_OK; // 接收數(shù)據(jù)標(biāo)志
bit B_TX1_Busy; // 發(fā)送忙標(biāo)志
/************* 本地函數(shù)聲明 **************/
void UART1_config(u32 brt, u8 timer, u8 io); // brt: 通信波特率, timer=2: 波特率使用定時(shí)器2, 其它值: 使用Timer1做波特率. io=0: 串口1切換到P3.0 P3.1, =1: 切換到P3.6 P3.7, =2: 切換到P1.6 P1.7, =3: 切換到P4.3 P4.4.
u8 Timer0_Config(u8 t, u32 reload); //t=0: reload值是主時(shí)鐘周期數(shù), t=1: reload值是時(shí)間(單位us), 返回0正確, 返回1裝載值過大錯(cuò)誤.
u16 MODBUS_CRC16(u8 *p, u8 n);
u8 MODBUS_RTU(void);
#define SL_ADDR 0x10 /* 本從機(jī)站號(hào)地址 */
#define REG_ADDRESS 0x1000 /* 寄存器首地址 */
#define REG_LENGTH 64 /* 寄存器長(zhǎng)度 */
u16 xdata modbus_reg[REG_LENGTH]; /* 寄存器地址 */
//========================================================================
// 函數(shù): void main(void)
// 描述: 主函數(shù)
// 參數(shù): none.
// 返回: none.
// 版本: VER1.0
// 日期: 2018-4-2
// 備注:
//========================================================================
void main(void)
{
u8 i;
u16 crc;
Timer0_Config(0, MAIN_Fosc / 10000); //t=0: reload值是主時(shí)鐘周期數(shù), (中斷頻率, 20000次/秒)
UART1_config(9600UL, 1, 0); // brt: 通信波特率, timer=2: 波特率使用定時(shí)器2, 其它值: 使用Timer1做波特率. io=0: 串口1切換到P3.0 P3.1, =1: 切換到P3.6 P3.7, =2: 切換到P1.6 P1.7, =3: 切換到P4.3 P4.4.
EA = 1;
while (1)
{
if(B_RX1_OK && !B_TX1_Busy) //收到數(shù)據(jù), 進(jìn)行MODBUS-RTU協(xié)議解析
{
if(MODBUS_CRC16(RX1_Buffer, RX1_cnt) == 0) //首先判斷CRC16是否正確, 不正確則忽略, 不處理也不返回信息
{
if((RX1_Buffer[0] == 0x00) || (RX1_Buffer[0] == SL_ADDR)) //然后判斷站號(hào)地址是否正確, 或者是否廣播地址(不返回信息)
{
if(RX1_cnt > 2) RX1_cnt -= 2; //去掉CRC16校驗(yàn)字節(jié)
i = MODBUS_RTU(); //MODBUS-RTU協(xié)議解析
if(i != 0) //錯(cuò)誤處理
{
TX1_Buffer[0] = SL_ADDR; //站號(hào)地址
TX1_Buffer[1] = i; //錯(cuò)誤代碼
crc = MODBUS_CRC16(TX1_Buffer, 2);
TX1_Buffer[2] = (u8)(crc>>8); //CRC是小端模式
TX1_Buffer[3] = (u8)crc;
B_TX1_Busy = 1; //標(biāo)志發(fā)送忙
TX1_cnt = 0; //發(fā)送字節(jié)計(jì)數(shù)
TX1_number = 4; //要發(fā)送的字節(jié)數(shù)
TI = 1; //啟動(dòng)發(fā)送
}
}
}
RX1_cnt = 0;
B_RX1_OK = 0;
}
}
}
/****************************** MODBUS_CRC (shift) *************** past test 06-11-27 *********
計(jì)算CRC,調(diào)用方式 MODBUS_CRC16(&CRC,8); &CRC為首地址,8為字節(jié)數(shù)
CRC-16 for MODBUS
CRC16=X16+X15+X2+1
TEST: ---> ABCDEFGHIJ CRC16=0x0BEE 1627T
*/
//========================================================================
// 函數(shù): u16 MODBUS_CRC16(u8 *p, u8 n)
// 描述: 計(jì)算CRC16函數(shù).
// 參數(shù): *p: 要計(jì)算的數(shù)據(jù)指針.
// n: 要計(jì)算的字節(jié)數(shù).
// 返回: CRC16值.
// 版本: V1.0, 2022-3-18 梁工
//========================================================================
u16 MODBUS_CRC16(u8 *p, u8 n)
{
u8 i;
u16 crc16;
crc16 = 0xffff; //預(yù)置16位CRC寄存器為0xffff(即全為1)
do
{
crc16 ^= (u16)*p; //把8位數(shù)據(jù)與16位CRC寄存器的低位相異或,把結(jié)果放于CRC寄存器
for(i=0; i<8; i++) //8位數(shù)據(jù)
{
if(crc16 & 1) crc16 = (crc16 >> 1) ^ 0xA001; //如果最低位為0,把CRC寄存器的內(nèi)容右移一位(朝低位),用0填補(bǔ)最高位,
//再異或多項(xiàng)式0xA001
else crc16 >>= 1; //如果最低位為0,把CRC寄存器的內(nèi)容右移一位(朝低位),用0填補(bǔ)最高位
}
p++;
}while(--n != 0);
return (crc16);
}
/********************* modbus協(xié)議 *************************/
/***************************************************************************
寫多寄存器
數(shù)據(jù): 地址 功能碼 寄存地址 寄存器個(gè)數(shù) 寫入字節(jié)數(shù) 寫入數(shù)據(jù) CRC16
偏移: 0 1 2 3 4 5 6 7~ 最后2字節(jié)
字節(jié): 1 byte 1 byte 2 byte 2 byte 1byte 2*n byte 2 byte
addr 0x10 xxxx xxxx xx xx....xx xxxx
返回
數(shù)據(jù): 地址 功能碼 寄存地址 寄存器個(gè)數(shù) CRC16
偏移: 0 1 2 3 4 5 6 7
字節(jié): 1 byte 1 byte 2 byte 2 byte 2 byte
addr 0x10 xxxx xxxx xxxx
讀多寄存器
數(shù)據(jù):站號(hào)(地址) 功能碼 寄存地址 寄存器個(gè)數(shù) CRC16
偏移: 0 1 2 3 4 5 6 7
字節(jié): 1 byte 1 byte 2 byte 2 byte 2 byte
addr 0x03 xxxx xxxx xxxx
返回
數(shù)據(jù):站號(hào)(地址) 功能碼 讀出字節(jié)數(shù) 讀出數(shù)據(jù) CRC16
偏移: 0 1 2 3~ 最后2字節(jié)
字節(jié): 1 byte 1 byte 1byte 2*n byte 2 byte
addr 0x03 xx xx....xx xxxx
返回錯(cuò)誤代碼
數(shù)據(jù):站號(hào)(地址) 錯(cuò)誤碼 CRC16
偏移: 0 1 最后2字節(jié)
字節(jié): 1 byte 1 byte 2 byte
addr 0x03 xxxx
***************************************************************************/
u8 MODBUS_RTU(void)
{
u8 i,j,k;
u16 reg_addr; //寄存器地址
u8 reg_len; //寫入寄存器個(gè)數(shù)
u16 crc;
if(RX1_Buffer[1] == 0x10) //寫多寄存器
{
if(RX1_cnt < 9) return 0x91; //命令長(zhǎng)度錯(cuò)誤
if((RX1_Buffer[4] != 0) || ((RX1_Buffer[5] *2) != RX1_Buffer[6])) return 0x92; //寫入寄存器個(gè)數(shù)與字節(jié)數(shù)錯(cuò)誤
if((RX1_Buffer[5]==0) || (RX1_Buffer[5] > REG_LENGTH)) return 0x92; //寫入寄存器個(gè)數(shù)錯(cuò)誤
reg_addr = ((u16)RX1_Buffer[2] << 8) + RX1_Buffer[3]; //寄存器地址
reg_len = RX1_Buffer[5]; //寫入寄存器個(gè)數(shù)
if((reg_addr+(u16)RX1_Buffer[5]) > (REG_ADDRESS+REG_LENGTH)) return 0x93; //寄存器地址錯(cuò)誤
if(reg_addr < REG_ADDRESS) return 0x93; //寄存器地址錯(cuò)誤
if((reg_len*2+7) != RX1_cnt) return 0x91; //命令長(zhǎng)度錯(cuò)誤
j = reg_addr - REG_ADDRESS; //寄存器數(shù)據(jù)下標(biāo)
for(k=7, i=0; i<reg_len; i++,j++)
{
modbus_reg[j] = ((u16)RX1_Buffer[k] << 8) + RX1_Buffer[k+1]; //寫入數(shù)據(jù), 大端模式
k += 2;
}
if(RX1_Buffer[0] != 0) //非廣播地址則應(yīng)答
{
for(i=0; i<6; i++) TX1_Buffer[i] = RX1_Buffer[i]; //要返回的應(yīng)答
crc = MODBUS_CRC16(TX1_Buffer, 6);
TX1_Buffer[6] = (u8)(crc>>8); //CRC是小端模式
TX1_Buffer[7] = (u8)crc;
B_TX1_Busy = 1; //標(biāo)志發(fā)送忙
TX1_cnt = 0; //發(fā)送字節(jié)計(jì)數(shù)
TX1_number = 8; //要發(fā)送的字節(jié)數(shù)
TI = 1; //啟動(dòng)發(fā)送
}
}
else if(RX1_Buffer[1] == 0x03) //讀多寄存器
{
if(RX1_Buffer[0] != 0) //非廣播地址則應(yīng)答
{
if(RX1_cnt != 6) return 0x91; //命令長(zhǎng)度錯(cuò)誤
if(RX1_Buffer[4] != 0) return 0x92; //讀出寄存器個(gè)數(shù)錯(cuò)誤
if((RX1_Buffer[5]==0) || (RX1_Buffer[5] > REG_LENGTH)) return 0x92; //讀出寄存器個(gè)數(shù)錯(cuò)誤
reg_addr = ((u16)RX1_Buffer[2] << 8) + RX1_Buffer[3]; //寄存器地址
reg_len = RX1_Buffer[5]; //讀出寄存器個(gè)數(shù)
if((reg_addr+(u16)RX1_Buffer[5]) > (REG_ADDRESS+REG_LENGTH)) return 0x93; //寄存器地址錯(cuò)誤
if(reg_addr < REG_ADDRESS) return 0x93; //寄存器地址錯(cuò)誤
j = reg_addr - REG_ADDRESS; //寄存器數(shù)據(jù)下標(biāo)
TX1_Buffer[0] = SL_ADDR; //站號(hào)地址
TX1_Buffer[1] = 0x03; //讀功能碼
TX1_Buffer[2] = reg_len*2; //返回字節(jié)數(shù)
for(k=3, i=0; i<reg_len; i++,j++)
{
TX1_Buffer[k++] = (u8)(modbus_reg[j] >> 8); //數(shù)據(jù)為大端模式
TX1_Buffer[k++] = (u8)modbus_reg[j];
}
crc = MODBUS_CRC16(TX1_Buffer, k);
TX1_Buffer[k++] = (u8)(crc>>8); //CRC是小端模式
TX1_Buffer[k++] = (u8)crc;
B_TX1_Busy = 1; //標(biāo)志發(fā)送忙
TX1_cnt = 0; //發(fā)送字節(jié)計(jì)數(shù)
TX1_number = k; //要發(fā)送的字節(jié)數(shù)
TI = 1; //啟動(dòng)發(fā)送
}
}
else return 0x90; //功能碼錯(cuò)誤
return 0; //解析正確
}
//========================================================================
// 函數(shù):u8 Timer0_Config(u8 t, u32 reload)
// 描述: timer0初始化函數(shù).
// 參數(shù): t: 重裝值類型, 0表示重裝的是系統(tǒng)時(shí)鐘數(shù), 其余值表示重裝的是時(shí)間(us).
// reload: 重裝值.
// 返回: 0: 初始化正確, 1: 重裝值過大, 初始化錯(cuò)誤.
// 版本: V1.0, 2018-3-5
//========================================================================
u8 Timer0_Config(u8 t, u32 reload) //t=0: reload值是主時(shí)鐘周期數(shù), t=1: reload值是時(shí)間(單位us)
{
TR0 = 0; //停止計(jì)數(shù)
if(t != 0) reload = (u32)(((float)MAIN_Fosc * (float)reload)/1000000UL); //重裝的是時(shí)間(us), 計(jì)算所需要的系統(tǒng)時(shí)鐘數(shù).
if(reload >= (65536UL * 12)) return 1; //值過大, 返回錯(cuò)誤
if(reload < 65536UL) AUXR |= 0x80; //1T mode
else
{
AUXR &= ~0x80; //12T mode
reload = reload / 12;
}
reload = 65536UL - reload;
TH0 = (u8)(reload >> 8);
TL0 = (u8)(reload);
ET0 = 1; //允許中斷
TMOD &= 0xf0;
TMOD |= 0; //工作模式, 0: 16位自動(dòng)重裝, 1: 16位定時(shí)/計(jì)數(shù), 2: 8位自動(dòng)重裝, 3: 16位自動(dòng)重裝, 不可屏蔽中斷
TR0 = 1; //開始運(yùn)行
return 0;
}
//========================================================================
// 函數(shù): void timer0_ISR (void) interrupt TIMER0_VECTOR
// 描述: timer0中斷函數(shù).
// 參數(shù): none.
// 返回: none.
// 版本: V1.0, 2016-5-12
//========================================================================
void timer0_ISR (void)interrupt TIMER0_VECTOR
{
if(RX1_TimeOut != 0)
{
if(--RX1_TimeOut==0) //超時(shí)
{
if(RX1_cnt != 0) //接收有數(shù)據(jù)
{
B_RX1_OK = 1; //標(biāo)志已收到數(shù)據(jù)塊
}
}
}
}
//========================================================================
// 函數(shù): SetTimer2Baudraye(u16 dat)
// 描述: 設(shè)置Timer2做波特率發(fā)生器。
// 參數(shù): dat: Timer2的重裝值.
// 返回: none.
// 版本: VER1.0
// 日期: 2018-4-2
// 備注:
//========================================================================
void SetTimer2Baudraye(u16 dat) // 選擇波特率, 2: 使用Timer2做波特率, 其它值: 使用Timer1做波特率.
{
AUXR &= ~(1<<4); //Timer stop
AUXR &= ~(1<<3); //Timer2 set As Timer
AUXR |= (1<<2); //Timer2 set as 1T mode
T2H = (u8)(dat >> 8);
T2L = (u8)dat;
IE2 &= ~(1<<2); //禁止中斷
AUXR |= (1<<4); //Timer run enable
}
//========================================================================
// 函數(shù): void UART1_config(u32 brt, u8 timer, u8 io)
// 描述: UART1初始化函數(shù)。
// 參數(shù): brt: 通信波特率.
// timer: 波特率使用的定時(shí)器, timer=2: 波特率使用定時(shí)器2, 其它值: 使用Timer1做波特率.
// io: 串口1切換到的IO, io=0: 串口1切換到P3.0 P3.1, =1: 切換到P3.6 P3.7, =2: 切換到P1.6 P1.7, =3: 切換到P4.3 P4.4.
// 返回: none.
// 版本: VER1.0
// 日期: 2018-4-2
// 備注:
//========================================================================
void UART1_config(u32 brt, u8 timer, u8 io) // brt: 通信波特率, timer=2: 波特率使用定時(shí)器2, 其它值: 使用Timer1做波特率. io=0: 串口1切換到P3.0 P3.1, =1: 切換到P3.6 P3.7, =2: 切換到P1.6 P1.7, =3: 切換到P4.3 P4.4.
{
brt = 65536UL - (MAIN_Fosc / 4) / brt;
if(timer == 2) //波特率使用定時(shí)器2
{
AUXR |= 0x01; //S1 BRT Use Timer2;
SetTimer2Baudraye((u16)brt);
}
else //波特率使用定時(shí)器1
{
TR1 = 0;
AUXR &= ~0x01; //S1 BRT Use Timer1;
AUXR |= (1<<6); //Timer1 set as 1T mode
TMOD &= ~(1<<6); //Timer1 set As Timer
TMOD &= ~0x30; //Timer1_16bitAutoReload;
TH1 = (u8)(brt >> 8);
TL1 = (u8)brt;
ET1 = 0; // 禁止Timer1中斷
INTCLKO&= ~0x02; // Timer1不輸出高速時(shí)鐘
TR1 = 1; // 運(yùn)行Timer1
}
if(io == 1) {S1_USE_P36P37(); P3n_standard(0xc0);} //切換到 P3.6 P3.7
else if(io == 2) {S1_USE_P16P17(); P1n_standard(0xc0);} //切換到 P1.6 P1.7
else if(io == 3) {S1_USE_P43P44(); P4n_standard(0x18);} //切換到 P4.3 P4.4
else {S1_USE_P30P31(); P3n_standard(0x03);} //切換到 P3.0 P3.1
SCON = (SCON & 0x3f) | (1<<6); // 8位數(shù)據(jù), 1位起始位, 1位停止位, 無校驗(yàn)
// PS = 1; //高優(yōu)先級(jí)中斷
ES = 1; //允許中斷
REN = 1; //允許接收
}
//========================================================================
// 函數(shù): void UART1_ISR (void) interrupt UART1_VECTOR
// 描述: 串口1中斷函數(shù)
// 參數(shù): none.
// 返回: none.
// 版本: VER1.0
// 日期: 2018-4-2
// 備注:
//========================================================================
void UART1_ISR (void) interrupt UART1_VECTOR
{
if(RI)
{
RI = 0;
if(!B_RX1_OK) //接收緩沖空閑
{
if(RX1_cnt >= RX1_Length) RX1_cnt = 0;
RX1_Buffer[RX1_cnt++] = SBUF;
RX1_TimeOut = 36; //接收超時(shí)計(jì)時(shí)器, 35個(gè)位時(shí)間
}
}
if(TI)
{
TI = 0;
if(TX1_number != 0) //有數(shù)據(jù)要發(fā)
{
SBUF = TX1_Buffer[TX1_cnt++];
TX1_number--;
}
else B_TX1_Busy = 0;
}
}
|
|