作为一名使用PIC单片机近三十年的工程师,我最早从PIC16F87X系列起步,后来为了应对小型化、低成本的项目需求,又陆续使用了6脚、8脚、14脚等PIC单片机。
在众多PIC单片机中,PIC16F1824和PIC12F1822是我认为最适合入门的两个型号。
它们不仅易于上手,而且引脚兼容性极佳——PIC16F1824的1-4脚和11-14脚与PIC12F1822完全重合,这意味着在设计初期,你可以灵活替换,无需重新绘制电路板。
今天就以PIC16F1824开发板为例,介绍一下内置的MODBUS-RTU功能码01/03/05/06的具体应用
一、MODBUS-RTU协议:
MODBUS-RTU最常用的功能码主要就是01/03/05/06,掌握了这4个其他功能码也就基本会用了。
具体内容可参考我的另一篇文章:MODBUS-RTU协议真不难!用四个核心功能码(01/03/05/06)带你玩转工业通讯(附代码)
二、EEPROM读写例程:
PIC单片机输入输出寄存器的控制字设置与其他如MSP430系列单片机不一样,一般其他系列单片机某一I/O口输出置1、输入置0;PIC则相反,输出置0、输入置1,你可以把0想成O(对应OUT),把1想成I(对应IN),这样会不容易设置错。
以下是MODBUS-RTU读写示例(16M主频),具体配置及程序如下:
#include<pic16f1824.h>
__CONFIG(0x083c); //配置字1寄存器
__CONFIG(0x1412); //配置字2寄存器
#define LED1 RA0
#define LED2 RA1
#define LED3 RC0
#define LED4 RC1
#define RELAY1 RC5
#define RELAY2 RC4
#define KEY1 RA3
#define KEY2 RC3
#define KEY3 RC2
unsigned char txi,rc_tnt,time_tnt,rxtime_flag,key_value;
unsigned int crc_hl, half_sec,time_2s,result_ad;
unsigned char trans[20];//发送缓存
unsigned char reciv[20];//接收缓存
unsigned char data_buf[20];//数据缓存
unsigned char eeprom_e2[12]; //EEPROM数据缓冲区
void delay(unsigned int v) //延时函数
{
while(v--){};
}
void uart_send(unsigned char dat)//串口发送数据
{
TXEN=1;
TXREG=dat;
while(!TRMT);
}
// EEPROM读函数:读取指定地址的数据
char eeprom_r(char addr)
{
char temp;
GIE=0; //关闭全局中断,避免读写过程被打断
EEADR=addr; //写入要读取的EEPROM地址(0-255)
RD=1; //触发读操作
while(RD); //等待读操作完成
temp=EEDATA; //取出读到的数据
GIE=1; //重新开启全局中断
return(temp);
}
// EEPROM写函数:向指定地址写入数据
void eeprom_w(char addr,char data)
{
GIE=0; //关闭全局中断,避免读写过程被打断
WREN=1; //使能写操作
EEADR=addr; //写入目标地址
EEDATA=data;//写入要存储的数据
//PIC单片机写EEPROM必须的两步
EECON2=0x55;
EECON2=0xaa;
WR=1; //触发写操作
while(WR); //等待写操作完成
WREN=0; //禁用写操作
GIE=1; //重新开启全局中断
}
void calccrc(uchar crcbuf) //MODBUS-RTU校验码算法子程序
{
uchar i;
crc_hl=crc_hl^crcbuf;
for(i=0;i<8;i++)
{
uchar TT;
TT=crc_hl&1;
crc_hl=crc_hl>>1;
crc_hl=crc_hl&0x7fff;
if(TT==1)
crc_hl=crc_hl^0xa001;
crc_hl=crc_hl&0xffff;
}
}
// 中断服务程序
void interrupt isr(void) {
if (TMR1IF) { // 判断是否为Timer1中断
TMR1L = 0xBF; // 重载定时初值(低位)
TMR1H = 0xF9; // 重载定时初值(高位)
half_sec++;
if (half_sec > 1249) { // 累计0.5秒
time_2s++; // 计数器+1
half_sec = 0;
}
if(rxtime_flag==1) //串口数据接收计时
{
time_tnt+=1;
}
TMR1IF = 0; // 清除中断标志
}
if(RCIF)//串口中断接收数据
{
reciv[rc_tnt]=RCREG;
rc_tnt+=1;
time_tnt=0;
rxtime_flag=1;
RCIF=0;
}
}
void main() {
unsigned char i,j;
// 初始化I/O
TRISA = 0x1A;
TRISC = 0x0C;
WPUA3=1;
PORTA = 0x00;
PORTC = 0x03; //RC0、RC1输出高电平,初始上电LED3、LED4为熄灭状态,RC5、RC4控制的两个继电器RELAY1、RELAY2为释放状态。
// 外设功能选择
APFCON0 = 0x84; // 设置RA0为TX,RA1为RX
// 模拟输入关闭(除指定引脚)
ANSELA = 0x10;
ANSELC = 0x00;
PIE1 = 0x01;
INTCON = 0x40; // 使能外围中断
OSCCON = 0x78; // 内部时钟16MHz
WDTCON=0x1c; //看门狗16秒
//以下为Timer1配置
T1CON = 0x60; // 预分频器设为1:4,Timer1关闭
TMR1IF = 0;
TMR1L = 0xBF; // 定时初值
TMR1H = 0xF9;
TMR1IE = 1; // 使能Timer1中断
T1CON |= 0x01; // 启动Timer1
//串口设置
SYNC=0; //异步模式
BRGH=1; //加速
BRG16=0;
SPEN=1; //允许串行通讯
CREN=1;
TXEN=1;
RCIE=1;
SPBRG=103; //9600波特率
// SPBRG=8; //115200波特率
PEIE=1;
GIE = 1; // 全局中断使能
for(i=0;i<4;i++)
{
eeprom_e2[i]=eeprom_r(i);
}
if(eeprom_e2[0]==0x66)
{
dres_485=eeprom_e2[1]; //读保存的地址
}
if(eeprom_e2[2]==0x77)
{
SPBRG=eeprom_e2[3]; //读保存的波特率
}
while(1) {
asm("clrwdt");
if(time_2s>3) //2秒采集一次18B20温度,一次AD转换
{
adc_ra4(0x0d);
data_buf[3]=result_ad>>8;
data_buf[4]=result_ad&0xff;
GetTemp();
wendu_18b20=(unsigned int)(Temper*10); //小数点后移一位
data_buf[5]=wendu_18b20>>8;
data_buf[6]=wendu_18b20&0xff;
time_2s=0;
}
if((rxtime_flag==1)&&(time_tnt>19))
{
crc_hl=0xffff;
for(txi=0;txi<(rc_tnt-2);txi++)
{
calccrc(reciv[txi]);
}
if(rc_tnt==8)
{
if(((reciv[0]==dres_485)||(reciv[0]==0))&&(reciv[1]==0x03)&&(reciv[6]==(crc_hl&0xff))&&(reciv[7]==(crc_hl>>8)))
{
if(reciv[0]==0) //读取本机地址
{
trans[0]=dres_485;
trans[1]=reciv[1];
trans[2]=2;
trans[3]=0;
trans[4]=dres_485;
crc_hl=0xffff;
for(txi=0;txi<5;txi++)
{
calccrc(trans[txi]);
}
trans[5]=crc_hl&0xff;
trans[6]=crc_hl>>8;
RA2=1;
for(txi=0;txi<7;txi++)
{
uart_send(trans[txi]);
}
RA2=0;
}
else //读数据
{
data_buf[0]=dres_485;
data_buf[1]=3;
data_buf[2]=4;
crc_hl=0xffff;
for(txi=0;txi<7;txi++)
{
calccrc(data_buf[txi]);
}
data_buf[7]=crc_hl&0xff;
data_buf[8]=crc_hl>>8;
RA2=1;
for(txi=0;txi<9;txi++)
{
uart_send(data_buf[txi]);//发送A/D采集与18B20温度数据
}
RA2=0; }
}
if((reciv[0]==dres_485)&&(reciv[1]==0x06)&&(reciv[6]==(crc_hl&0xff))&&(reciv[7]==(crc_hl>>8)))
{
if((reciv[2]==0)&&(reciv[3]==100)) //本机地址设置
{
eeprom_e2[0]=0x66;
eeprom_e2[1]=dres_485=reciv[5];
for(i=0;i<2;i++)
{
eeprom_w(i,eeprom_e2[i]);
}
}
if((reciv[2]==0)&&(reciv[3]==100)) //本机波特率设置
{
eeprom_e2[2]=0x77;
eeprom_e2[3]=SPBRG=reciv[5];
for(i=2;i<4;i++)
{
eeprom_w(i,eeprom_e2[i]);
}
}
RA2=1;
for(txi=0;txi<8;txi++)
{
uart_send(reciv[txi]);
}
RA2=0;
}
if((reciv[0]==dres_485)&&(reciv[1]==0x01)&&(reciv[6]==(crc_hl&0xff))&&(reciv[7]==(crc_hl>>8)))
{
trans[0]=dres_485;
trans[1]=1;
trans[2]=1;
trans[3]=flag_relay_on;
crc_hl=0xffff;
for(txi=0;txi<4;txi++)
{
calccrc(trans[txi]);
}
trans[4]=crc_hl&0xff;
trans[5]=crc_hl>>8;
RA2=1;
for(txi=0;txi<6;txi++)
{
uart_send(trans[txi]);
}
RA2=0;
}
if((reciv[0]==dres_485)&&(reciv[1]==0x05)&&(reciv[6]==(crc_hl&0xff))&&(reciv[7]==(crc_hl>>8)))
{
if(reciv[3]==0)
{
if(reciv[4]==0xff) //启动继电器1
{
flag_relay_on&=0xfe;
flag_relay_on|=1;
RELAY1=1;
}
if(reciv[4]==0) //停止
{
flag_relay_on&=0xfe;
RELAY1=0;
}
}
if(reciv[3]==1)
{
if(reciv[4]==0xff) //启动继电器2
{
flag_relay_on&=0xfd;
flag_relay_on|=2;
RELAY2=1;
}
if(reciv[4]==0) //停止
{
flag_relay_on&=0xfd;
RELAY2=0;
}
}
RA2=1;
for(txi=0;txi<8;txi++)
{
uart_send(reciv[txi]);
}
RA2=0;
}
}
rc_tnt=0;
time_tnt=0;
rxtime_flag=0;
}
//可加入其他任务
}
}
三、本系列文章规划
本文是《PIC单片机入门实战》系列的第一篇,后续将逐步展开以下内容,带你从零构建一个完整的嵌入式控制系统:
|
序号 |
主题 |
内容概要 |
|
1 |
振荡器与Timer1 |
时钟配置与定时中断 |
|
2 |
I/O输出控制 |
驱动2路继电器与4路LED |
|
3 |
I/O按键输入 |
3路按键扫描与电平变化中断 |
|
4 |
UART通信 |
RS232/RS485数据交互 |
|
5 |
A/D转换应用 |
1路电位器电压读取 |
|
6 |
温度传感器采集 |
1路DS18B20输入 |
|
7 |
EEPROM存储 |
数据存储与读取 |
|
8 |
MODBUS-RTU集成 |
功能码01/03/05/06数据读写 |
《PIC单片机入门实战》这8片文章内容来源于我自己画的电路原理图及程序,有对PIC单片机感兴趣想学习的朋友可以关注我,免费赠送资料(包括原理图、数据手册、各种例程等)。
有需要这款开发板的朋友也可以关注联系我。
后续干货不断,咱们一起在单片机的世界里,共同进步。
:PIC16F1824PIC12F1822,MODBUS功能码01030506应用&spm=1001.2101.3001.5002&articleId=157181610&d=1&t=3&u=cfbbaf50974c491ea5742b0dba7a9372)
1万+

被折叠的 条评论
为什么被折叠?



