這裡為什麼說RS485是MODBUS的實作載體呢,因為RS485僅僅隻是一種硬體電平标準,我的了解就是在序列槽的基礎上加了電平轉換晶片比如MAX485,将序列槽的TTL電平轉換成了RS485差分電平。
貼上代碼段
1.RS485——序列槽2初始化
void RS485_USART2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOG,ENABLE);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART2);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_USART2);
//GPIOA2,A3初始化設定
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//複用模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽輸出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//複用模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽輸出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOG, &GPIO_InitStructure);//初始化
//序列槽初始化配置
USART_InitStruct.USART_BaudRate=9600;
USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
USART_InitStruct.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
USART_InitStruct.USART_Parity=USART_Parity_No;
USART_InitStruct.USART_StopBits=USART_StopBits_1;
USART_InitStruct.USART_WordLength=USART_WordLength_8b;
USART_Init(USART2,&USART_InitStruct);
USART_Cmd(USART2,ENABLE);
USART_ClearFlag(USART2, USART_FLAG_TC);
USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
NVIC_InitStruct.NVIC_IRQChannel=USART2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=0;
NVIC_Init(&NVIC_InitStruct);
GPIO_ResetBits(GPIOG,GPIO_Pin_8);//初始化為接收狀态
}
因為需要控制晶片引腳收發,是以要利用PG8來控制接收或者發送使能,具體的硬體原理圖于正點原子F407的相同,大家可以參考那個。
定時器初始化
void Timer2_Init(void)//84M
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruc;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
TIM_TimeBaseInitStruc.TIM_Period=1000-1;//1ms
TIM_TimeBaseInitStruc.TIM_Prescaler=84-1;//84M/84=1MHZ->1us
TIM_TimeBaseInitStruc.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStruc.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruc);
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
TIM_Cmd(TIM2,ENABLE);
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
}
void Timer2_NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
NVIC_InitStruct.NVIC_IRQChannel=TIM2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=3;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=3;
NVIC_Init(&NVIC_InitStruct);
}
發送位元組與發送多個位元組函數
void RS485_SendByte(u8 bbyte)
{
RS_485_TX;
USART_SendData(USART2, bbyte);
while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);
RS_485_RX;
}
void RS485_SendData(u8 *Sdbuf,u8 len)
{
u8 i;
RS_485_TX;
for(i=0;i<len;i++)
{
USART_SendData(USART2,Sdbuf[i]);
while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);
}
while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);
RS_485_RX;
}
我認為這些都是基礎知識,大家應該首先根據正點原子把能夠利用485收發全部實作,然後再這個基礎上加入485協定。
接下來就是序列槽的接收以及定時器中斷函數,modbus的複雜主要就麻煩在如何将序列槽的接收中斷與定時器中斷配合好,達到完整的接收一幀資料,并且不影響下一幀接受的效果。
void USART2_IRQHandler()
{
u8 res;
if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)
{
res =USART_ReceiveData(USART2);
modbus.ReceiveBuff[modbus.ReceiveCount]=res;
modbus.ReceiveCount++;
if(modbus.ReceiveCount==1)
{
modbus.timerun=1;
}
modbus.timecount=0;
}
}
void TIM2_IRQHandler()
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
{
if(modbus.timerun==1)
{
modbus.timecount++;
}
if(modbus.timecount>=5)
{
modbus.timerun=0;
modbus.timecount=0;
modbus.ReceiveComplete=1;
}
}
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
在這裡為了友善,定義了一個modbus的結構體
typedef struct
{
u8 Slave_ID; //從機ID
u8 ReceiveBuff[20]; //接收緩存數組
u8 ReceiveCount; //計算接收到的資料有多少位元組
u8 timecount; //有多久沒有接收到位元組,資料斷續的時間
u8 timerun; //斷續時間是否開始累加
u8 ReceiveComplete; //一幀資料接收完成标志
}MODBUS;
modbus協定主要就是,在接收時有3.5個字元間隔,那麼就認為這一幀資料接收結束,可以進行處理并且接收下一幀資料。
在這裡定義了一個接收緩存數組,用于将序列槽2接收到的資料放到緩存數組中。
大概邏輯是這樣的,首先主機發送指令後,從機傳回一幀資料,當接收到一幀資料的第一個字元時,計時器開始計時,timrun啟動,timcount++,如果在3.5個字元内又收到了下一個位元組資料,那麼timcount就會置0,使他無法到達3.5個字元間隔,如果接收到了,就将資料繼續放入到數組中。直到接收完畢,定時器到達3,5個字元間隔,此時接收完成ReceiveComplete=1;然後這個時候為了防止下一幀資料進來把資料覆寫掉,需要把接收緩存數組ReceiveBuff中的資料copy出去,
copy資料的函數
void RS485_Receive_Data(u8 *buf,u8 *len)
{
u8 i;
u8 Temp_len;
Temp_len=modbus.ReceiveCount;
if(modbus.ReceiveComplete==1)
{
for(i=0;i<Temp_len;i++)
{
buf[i]=modbus.ReceiveBuff[i];
}
*len=modbus.ReceiveCount;
modbus.ReceiveCount=0;
}
modbus.ReceiveComplete=0;
}
copy完後主程式處理資料,中斷繼續接收。
總的程式在這裡。有積分的話支援一下,沒積分的話評論區留下郵箱,我抽空發你。畢竟自己學習新東西也需要積分。算是形成一個良性循環吧。
程式連接配接https://download.csdn.net/download/zwbzero/19266609
用到的調試軟體連結https://download.csdn.net/download/zwbzero/19066229