知识点1【CRC校验】





CRC校验生成网址
知识点2【代码演示】
代码书写思路

代码演示
main.c
cpp
#include "stm32f10x.h"
#include "stm32f10x_conf.h"
#include "rs485.h"
int main(void)
{
//优先级组的配置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
RS485_GPIO_Init();
USART1_Config(9600);
USART2_Config(9600);
printf("你好\\n");
while(1)
{
Modbus_Init();
}
}
rs485.h
cpp
#ifndef _RS485_H_
#define _RS485_H_
#include "stm32f10x.h"
#include "stm32f10x_conf.h"
#include "stdio.h"
#include "string.h"
//设备ID
#define SLAVE_ID 0xFE
//串口相关宏
#define GPIO_USART1_2_TXRX GPIOA
#define PIN_USART1_TX GPIO_Pin_9
#define PIN_USART1_RX GPIO_Pin_10
#define PIN_USART2_TX GPIO_Pin_2
#define PIN_USART2_RX GPIO_Pin_3
//RS485使能相关宏
#define GPIO_RS485_ENABLE GPIOD
#define PIN_RS485_ENABLE GPIO_Pin_7
#define RS485_DE() (GPIOD->ODR |= (0x01 << 7))
#define RS485_RE() (GPIOD->ODR &= ~(0x01 << 7))
typedef struct{
u8 recv_data[256];
u8 send_data[256];
u8 recv_size;
u8 send_size;
u8 flag; //数据接收完成标志位,1:接收完成,0:等待接收完成
}DATA_RS485;
typedef struct{
u16 recv_offset;
u8 send_data[256];
u16 recv_size;
u8 send_size_byte;//用来计算总发送的字节数
}CMD_03;
void USART1_Config(u16 baud);
void USART2_Config(u16 baud);
void RS485_GPIO_Init(void);
void Modbus_Init(void);
void CMD03_Fun(CMD_03 *data);
void USART1_SendByte(u8 data);
void USART2_SendByte(u8 data);
void USART2_SendStr(u8 *data,u8 len);
int fputc(int c,FILE *stream);
uint16_t ModBus_CRC16(uint8_t *data, uint16_t length);
#endif
rs485.c
cpp
#include "rs485.h"
DATA_RS485 data_rs485 = {0};
CMD_03 data_cmd03 = {0};
//数据
u16 Server_data[] = {113,792,5564,56546,6546,5646,546,156,23,21};
void USART1_Config(u16 baud)//PA9 TX PB10 RX
{
GPIO_InitTypeDef GPIO_InitStruct;
USART_InitTypeDef USART_InitStruct;
//时钟配置
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
//模式配置
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = PIN_USART1_TX;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = PIN_USART1_RX;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA,&GPIO_InitStruct);
//串口配置
USART_StructInit(&USART_InitStruct);
USART_InitStruct.USART_BaudRate = baud;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1,&USART_InitStruct);
//串口使能
USART_Cmd(USART1,ENABLE);
printf("USART1 is ok!\\n");
}
void USART2_Config(u16 baud)//PA2 TX PA3 RX
{
GPIO_InitTypeDef GPIO_InitStruct;
USART_InitTypeDef USART_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
//时钟配置
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);
//模式配置
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = PIN_USART2_TX;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIO_USART1_2_TXRX,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = PIN_USART2_RX;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIO_USART1_2_TXRX,&GPIO_InitStruct);
//串口配置
USART_StructInit(&USART_InitStruct);
USART_InitStruct.USART_BaudRate = baud;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
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);
//中断配置
NVIC_InitStruct.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x01;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x01;
NVIC_Init(&NVIC_InitStruct);
//中断使能
USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
USART_ITConfig(USART2,USART_IT_IDLE,ENABLE);
//串口使能
USART_Cmd(USART2,ENABLE);
printf("USART2 is ok!\\n");
}
void RS485_GPIO_Init(void)//PD7
{
GPIO_InitTypeDef GPIO_InitStruct;
//时钟配置
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD,ENABLE);
//模式配置
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStruct.GPIO_Pin = PIN_RS485_ENABLE;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIO_RS485_ENABLE,&GPIO_InitStruct);
//接收使能
RS485_RE();
}
void Modbus_Init(void)
{
int i = 0;
u8 data_addr = 0;
u8 data_fun = 0;
u16 data_CRC = 0;
u16 data_modbus_CRC = 0;
//判断数据是否接收完成
while(!data_rs485.flag);
data_rs485.flag = 0;
for(i = 0;i < data_rs485.recv_size;i++)
{
printf("%02x ",data_rs485.recv_data[i]);
}
printf("\\r\\n");
//接收到的数据解包
//此处仅解析:校验位,地址码,功能码
//FE 03 00 04 00 03 50 05(CRC)
data_addr = data_rs485.recv_data[0];
data_fun = data_rs485.recv_data[1];
//data_rs485.recv_size - 2 ------------ 低字节,data_rs485.recv_size - 2 ------------ 高字节
data_CRC = data_rs485.recv_data[data_rs485.recv_size - 2] | (data_rs485.recv_data[data_rs485.recv_size - 1] << 8);
printf("data_CRC = %04x\\r\\n",data_CRC);
data_modbus_CRC = ModBus_CRC16(data_rs485.recv_data,data_rs485.recv_size - 2);
printf("data_modbus_CRC = %04x\\r\\n",data_modbus_CRC);
//从机地址位
if(data_addr != SLAVE_ID)
{
printf("从机地址不符\\n");
return;
}
//数据的正确性判断
//校验位
if(data_modbus_CRC != data_CRC)
{
printf("校验位错误\\n");
return;
}
//功能码判断
switch(data_fun)
{
case 0x01:
break;
case 0x02:
break;
case 0x03:
//FE 03 00 04 00 03 50 05(CRC)中的00 04 00 03 解包到 data_cmd03.recv_data
//数据提取
data_cmd03.recv_offset = (data_rs485.recv_data[2] << 8) | data_rs485.recv_data[3];
data_cmd03.recv_size = (data_rs485.recv_data[4] << 8) | data_rs485.recv_data[5];
CMD03_Fun(&data_cmd03);
break;
case 0x06:
break;
case 0x16:
break;
case 0x20:
break;
}
//清空结构体,防止后续数据错误
memset(&data_rs485,0,sizeof(data_rs485));
memset(&data_cmd03,0,sizeof(data_cmd03));
}
//功能码03的处理函数
void CMD03_Fun(CMD_03 *data)
{
int i = 0;
u16 modbus_CRC;
//FE 03 00 04 00 03 50 05(CRC)
//u16 Server_data[] = {113,792,5564,56546,6546,5646,546,156,23,21};
//可知我们要提取的数据是:6546,5646,546
if(data->recv_offset + data->recv_size > sizeof(data_cmd03)/sizeof(u16))
{
printf("请求大小错误\\n");
return;
}
//发送数据组包
//从机ID
data->send_data[data->send_size_byte++] = SLAVE_ID;
//功能码
data->send_data[data->send_size_byte++] = data_rs485.recv_data[1];
//大小
data->send_data[data->send_size_byte++] = data->recv_size * 2;
//数据
for(i = 0;i < data->recv_size;i++)
{
data->send_data[data->send_size_byte++] = Server_data[i]/256;
data->send_data[data->send_size_byte++] = Server_data[i]%256;
}
printf("%u\\n",data->send_size_byte);
for(i = 0;i < data->send_size_byte;i++)
{
printf("%02x ",data->send_data[i]);
}
printf("\\r\\n");
//此时的data->send_size_byte刚好为要进行校验的总大小
//校验位
modbus_CRC = ModBus_CRC16(data->send_data,data->send_size_byte);
printf("modbus_CRC = %x\\n",modbus_CRC);
//校验位处理
data->send_data[data->send_size_byte++] = modbus_CRC%256;
data->send_data[data->send_size_byte++] = modbus_CRC/256;
USART2_SendStr(data->send_data,data->send_size_byte);
}
void USART1_SendByte(u8 data)
{
USART1->DR = data;
//while(!USART_GetFlagStatus(USART1,USART_FLAG_TC));
while(!USART_GetFlagStatus(USART1,USART_FLAG_TXE));
}
void USART2_SendByte(u8 data)
{
USART2->DR = data;
//while(!USART_GetFlagStatus(USART2,USART_FLAG_TC));
while(!USART_GetFlagStatus(USART2,USART_FLAG_TXE));
}
void USART2_SendStr(u8 *data,u8 len)
{
int i = 0;
RS485_DE();
for(i = 0;i < len;i++)
{
USART2_SendByte(data[i]);
}
while (!(USART2->SR & USART_FLAG_TC));
RS485_RE();
}
int fputc(int c,FILE *stream)
{
USART1_SendByte((u8)c);
return c;
}
//CRC校验算法函数
uint16_t ModBus_CRC16(uint8_t *data, uint16_t length)
{
uint16_t i;
uint16_t crc_value = 0xffff;
while (length--)
{
crc_value ^= *data++;
for(i = 0; i < 8; i++)
{
if (crc_value & 0x0001 )
crc_value = (crc_value >> 1) ^ 0xA001;
else
crc_value = crc_value >> 1;
}
}
return crc_value;
}
//USART2中断处理函数
void USART2_IRQHandler(void)
{
//接收中断
u8 data;
if(USART_GetITStatus(USART2,USART_IT_RXNE))
{
USART_ClearITPendingBit(USART2,USART_IT_RXNE);
//将Modbus数据帧写入 接收数组当中
data = USART2->DR; //进行数据缓冲,防止数据覆盖
data_rs485.recv_data[data_rs485.recv_size] = data;
data_rs485.recv_size++;
}
//空闲中断
if(USART_GetITStatus(USART2,USART_IT_IDLE))
{
USART2->SR;
//接收的动作
USART2->DR;
//标志位置1
data_rs485.flag = 1;
}
}
代码运行结果展示

代码问题
1、TC和TXE的使用问题

现象:

如果使用注释内的TC,就会出现第一个字符打印实物的原因,这是我查找资料,找到的原因:
在 逐字节发送 (fputc
、printf
)这种场景下,推荐用 TXE,它既不会阻塞得太久,也能保证每个字节都被正确推送到发送机里,不会弄断多字节编码的连续性。
当我们数据错误的时候可以把这个当作一个解决问题的方向
2、使能位的切换问题

3、逻辑错误,由于modbus发送的报文可能有0x00,因此不能这样

4、空闲中断的清除方法(重要)
下面是错误的

正确方法:
空闲中断的清除,需要一个接收数据的动作,与接收中断不一样。

读取状态寄存器只是走一个形式,最主要的是读取数据寄存器即DR
5、出现脏值
出现脏值的原因,即data->send_size_byte的值不对
因为每次 传输完成后都没有将
data_cmd03 以及 data_rs485结构体清0
现象:

修改方式:
Modbus_Init函数的结尾

结束
我最近在调整我的代码风格,各位如果有什么好的建议,可以私信或者评论,我会积极采纳,谢谢大家,希望我们能够一起进步!!!
希望大家能从我的代码中提取重点,错误中吸取经验!
代码重在练习!
代码重在练习!
代码重在练习!
今天的分享就到此结束了,希望对你有所帮助,如果你喜欢我的分享,请点赞收藏加关注,谢谢大家!!!