【嵌入式模块芯片开发】HART协议调制解调芯片AD5700配置流程
AD5700
XTAL_EN接地,CLK_CFG的两个引脚由同一个GPIO控制
初始时HART_CLK_CFG输出低电平
由RTS引脚控制调制/解调。当RTS处于高电平时,为解调(输入);否则为调制(输出)。切换时,延时1ms
HART协议部分,符合HART物理层要求(修订版8.1)。
一个标准UART帧,该帧包含一个起始位、8位数据、一个奇偶校验和一个停止位
UART:1200波特率 1停止位 无校验 数据长度8
AD5700与MCU的串口通信中 是没有校验位的 但是AD5700的HART协议部分是严格按照HART协议规范来的 所以有校验位 (别杠 不信可以去买个AD5700打一下示波器)
开始运行
- 检测HART_VDD,至少大于2.7V
- 引脚HART_CLK_CFG初始时为低电平,拉高后CLKOUT输出1.2288MHz,用定时器进行捕获,计算频率是否达标
- 最后开启调制或解调
初始化 - 使能CFG引脚,进行时钟检测
- 采用定时器捕获的方式计算频率(计算速度不够则可能算不准)
- 关闭CFG引脚
- 解调模式接收数据
- 当串口接收中断发生时,解析命令并切换调制模式,返回数据
代码例程
uint8_t UART4_RxBuffer[256]={0};
uint8_t UART4_RxFlag=0;
/**
* @brief UART4 Initialization Function
* @param None
* @retval None
*/
static void MX_UART4_Init(void)
{
/* USER CODE BEGIN UART4_Init 0 */
/* USER CODE END UART4_Init 0 */
/* USER CODE BEGIN UART4_Init 1 */
/* USER CODE END UART4_Init 1 */
huart4.Instance = UART4;
huart4.Init.BaudRate = 1200;
huart4.Init.WordLength = UART_WORDLENGTH_8B;
huart4.Init.StopBits = UART_STOPBITS_1;
huart4.Init.Parity = UART_PARITY_NONE;
huart4.Init.Mode = UART_MODE_TX_RX;
huart4.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart4.Init.OverSampling = UART_OVERSAMPLING_16;
huart4.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart4.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_UART_Init(&huart4) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN UART4_Init 2 */
HAL_UART_Receive_IT(&huart4,UART4_RxBuffer,1);
/* USER CODE END UART4_Init 2 */
}
/*!
* @brief 初始化AD5421
*
* @param None
*
* @return None
*/
void Init_AD5421(void)
{
uint16_t offset = 0;
uint16_t gain = 0;
Ctrl_AD5421(&hspi2,AD5421_RESET,0,true);
delay_us(50);
Ctrl_AD5421(&hspi2,AD5421_Control,0xFC00,true);
offset = Ctrl_AD5421(&hspi2,AD5421_Offset_Adjust,0,false);
gain = Ctrl_AD5421(&hspi2,AD5421_Gain_Adjust,0,false);
Ctrl_AD5421_DAC(20,true,offset,gain);
Ctrl_AD5421_ADC(1,true);
Ctrl_AD5421_ADC(0,true);
}
/*!
* @brief 使能HART_CLK_OUT的定时器,用于输入捕获,上升沿捕获,4次有效
*
* @param None
*
* @return freq: 浮点型频率变量,单位为Hz
*/
float Get_HART_CLK_Cycle(void)
{
uint32_t Count=0;
float freq=0;
TIM3_CH2_Flag=0;
TIM3_CH2_Overflow=0;
TIM3_CH2_Count[0]=0;
TIM3_CH2_Count[1]=0;
TIM_RESET_CAPTUREPOLARITY(&htim3,TIM_CHANNEL_2);
__HAL_TIM_SET_CAPTUREPOLARITY(&htim3, TIM_CHANNEL_2, TIM_INPUTCHANNELPOLARITY_FALLING);
HAL_TIM_Base_Start_IT(&htim3);
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_2);
while(TIM3_CH2_Flag<2);
TIM3_CH2_Flag=0;
Count=TIM3_CH2_Overflow*(0xFFFF+1)+TIM3_CH2_Count[1]-TIM3_CH2_Count[0];
freq=50000000.0f/Count;
TIM3_CH2_Overflow=0;
TIM3_CH2_Count[0]=0;
TIM3_CH2_Count[1]=0;
printf("[INFO] HART_CLK: %0.4f Hz\n",freq);
return freq;
}
/*!
* @brief 操作AD5700的HART调制/解调
*
* @param [in] dat_buf: 要写入的数据,可以填NULL
* [in] size: 要写入的数据个数,单位byte
* [in] WriteNotRead: true,写入(调制),发送UART数据然后通过HART发出来
* false,读取(解调),接收HART数据,解调以后以中断的方式接收UART数据
*
* @return None
*/
void Ctrl_AD5700_HART(uint8_t *dat_buf,uint8_t size,bool WriteNotRead)
{
if(WriteNotRead)
{
Disable_AD5700_RTS;
delay_ms(1);
HAL_UART_Transmit(&huart4,dat_buf,size,0xFFFF);
}
else
{
Enable_AD5700_RTS;
delay_ms(1);
HAL_UART_Receive_IT(&huart4,&UART4_RxBuffer[UART4_RxFlag],1); //开启中断 填入的buf最好是HART专用的数据buf 全局变量 然后在回调里面也改成HART的buf
}
}
/*!
* @brief 初始化AD5700
*
* @param None
*
* @return None
*/
void Init_AD5700(void)
{
//ADC检测
Enable_HART_CLK_CFG;
// //时钟检测
// Get_HART_CLK_Cycle();
// Disable_HART_CLK_CFG;
Ctrl_AD5700_HART(NULL,0,false);
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart==&huart2)
{
HAL_UART_Transmit(&huart2,&RxBuffer,1,0xFFFF);
HAL_UART_Receive_IT(&huart2,&RxBuffer,1);
}
else if(huart==&huart3)
{
HAL_UART_Transmit(&huart3,&RxBuffer,1,0xFFFF);
HAL_UART_Receive_IT(&huart3,&RxBuffer,1);
}
else if(huart==&huart4)
{
// HAL_UART_Transmit(&huart4,&UART4_RxBuffer[UART4_RxFlag],1,0xFFFF);
UART4_RxFlag++;
HAL_UART_Receive_IT(&huart4,&UART4_RxBuffer[UART4_RxFlag],1);
}
else if(huart==&huart5)
{
HAL_UART_Transmit(&huart5,&RxBuffer,1,0xFFFF);
if((RxBuffer>>5)==6)
{
load_dac=(RxBuffer&0x1F);
}
HAL_UART_Receive_IT(&huart5,&RxBuffer,1);
}
}
附录:压缩字符串、大小端格式转换
压缩字符串
首先HART数据格式如下:
重点就是浮点数和字符串类型
Latin-1就不说了 基本用不到
浮点数
浮点数里面 如 0x40 80 00 00表示4.0f
在HART协议里面 浮点数是按大端格式发送的 就是高位先发送 低位后发送
发送出来的数组为:40,80,00,00
但在C语言对浮点数的存储中 是按小端格式来存储的 也就是40在高位 00在低位
浮点数:4.0f
地址0x1000对应00
地址0x1001对应00
地址0x1002对应80
地址0x1003对应40
若直接使用memcpy函数 则需要进行大小端转换 否则会存储为:
地址0x1000对应40
地址0x1001对应80
地址0x1002对应00
地址0x1003对应00
大小端转换:
void swap32(void * p)
{
uint32_t *ptr=p;
uint32_t x = *ptr;
x = (x << 16) | (x >> 16);
x = ((x & 0x00FF00FF) << 8) | ((x >> 8) & 0x00FF00FF);
*ptr=x;
}
压缩Packed-ASCII字符串
本质上是将原本的ASCII的最高2位去掉 然后拼接起来 比如空格(0x20)
四个空格拼接后就成了
1000 0010 0000 1000 0010 0000
十六进制:82 08 20
对了一下表 0x20之前的识别不了
也就是只能识别0x20-0x5F的ASCII表
压缩/解压函数后面再写:
//传入的字符串和数字必须提前声明 且字符串大小至少为str_len 数组大小至少为str_len%4*3 str_len必须为4的倍数
uint8_t Trans_ASCII_to_Pack(uint8_t * str,uint8_t * buf,const uint8_t str_len)
{
if(str_len%4)
{
return 0;
}
uint8_t i=0;
memset(buf,0,str_len/4*3);
for(i=0;i<str_len;i++)
{
if(str[i]==0x00)
{
str[i]=0x20;
}
}
for(i=0;i<str_len/4;i++)
{
buf[3*i]=(str[4*i]<<2)|((str[4*i+1]>>4)&0x03);
buf[3*i+1]=(str[4*i+1]<<4)|((str[4*i+2]>>2)&0x0F);
buf[3*i+2]=(str[4*i+2]<<6)|(str[4*i+3]&0x3F);
}
return 1;
}
//传入的字符串和数字必须提前声明 且字符串大小至少为str_len 数组大小至少为str_len%4*3 str_len必须为4的倍数
uint8_t Trans_Pack_to_ASCII(uint8_t * str,uint8_t * buf,const uint8_t str_len)
{
if(str_len%4)
{
return 0;
}
uint8_t i=0;
memset(str,0,str_len);
for(i=0;i<str_len/4;i++)
{
str[4*i]=(buf[3*i]>>2)&0x3F;
str[4*i+1]=((buf[3*i]<<4)&0x30)|(buf[3*i+1]>>4);
str[4*i+2]=((buf[3*i+1]<<2)&0x3C)|(buf[3*i+2]>>6);
str[4*i+3]=buf[3*i+2]&0x3F;
}
return 1;
}
大小端转换
在串口等数据解析中 难免遇到大小端格式问题
什么是大端和小端
所谓的大端模式,就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
所谓的小端模式,就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
简单来说:大端——高尾端,小端——低尾端
举个例子,比如数字 0x12 34 56 78在内存中的表示形式为:
1)大端模式:
低地址 -----------------> 高地址
0x12 | 0x34 | 0x56 | 0x78
2)小端模式:
低地址 ------------------> 高地址
0x78 | 0x56 | 0x34 | 0x12
可见,大端模式和字符串的存储模式类似。
数据传输中的大小端
比如地址位、起止位一般都是大端格式
如:
起始位:0x520A
则发送的buf应为{0x52,0x0A}
而数据位一般是小端格式(单字节无大小端之分)
如:
一个16位的数据发送出来为{0x52,0x0A}
则对应的uint16_t类型数为: 0x0A52
而对于浮点数4.0f 转为32位应是:
40 80 00 00
以大端存储来说 发送出来的buf就是依次发送 40 80 00 00
以小端存储来说 则发送 00 00 80 40
由于memcpy等函数 是按字节地址进行复制 其复制的格式为小端格式 所以当数据为小端存储时 不用进行大小端转换
如:
uint32_t dat=0;
uint8_t buf[]={0x00,0x00,0x80,0x40};
memcpy(&dat,buf,4);
float f=0.0f;
f=*((float*)&dat); //地址强转
printf("%f",f);
或更优解:
uint8_t buf[]={0x00,0x00,0x80,0x40};
float f=0.0f;
memcpy(&f,buf,4);
而对于大端存储的数据(如HART协议数据 全为大端格式) 其复制的格式仍然为小端格式 所以当数据为小端存储时 要进行大小端转换
如:
uint32_t dat=0;
uint8_t buf[]={0x40,0x80,0x00,0x00};
memcpy(&dat,buf,4);
float f=0.0f;
swap32(&dat); //大小端转换
f=*((float*)&dat); //地址强转
printf("%f",f);
或:
uint8_t buf[]={0x40,0x80,0x00,0x00};
memcpy(&dat,buf,4);
float f=0.0f;
swap32(&f); //大小端转换
printf("%f",f);
或更优解:
uint32_t dat=0;
uint8_t buf[]={0x40,0x80,0x00,0x00};
float f=0.0f;
dat=(buf[0]<<24)|(buf[0]<<16)|(buf[0]<<8)|(buf[0]<<0)
f=*((float*)&dat);
总结
固 若数据为小端格式 则可以直接用memcpy函数进行转换 否则通过移位的方式再进行地址强转
对于多位数据 比如同时传两个浮点数 则可以定义结构体之后进行memcpy复制(数据为小端格式)
对于小端数据 直接用memcpy写入即可 若是浮点数 也不用再进行强转
对于大端数据 如果不嫌麻烦 或想使代码更加简洁(但执行效率会降低) 也可以先用memcpy写入结构体之后再调用大小端转换函数 但这里需要注意的是 结构体必须全为无符号整型 浮点型只能在大小端转换写入之后再次强转 若结构体内采用浮点型 则需要强转两次
所以对于大端数据 推荐通过移位的方式来进行赋值 然后再进行个别数的强转 再往通用结构体进行写入
多个不同变量大小的结构体 要主要字节对齐的问题
可以用#pragma pack(1) 使其对齐为1
但会影响效率
大小端转换函数
直接通过对地址的操作来实现 传入的变量为32位的变量
中间变量ptr是传入变量的地址
void swap16(void * p)
{
uint16_t *ptr=p;
uint16_t x = *ptr;
x = (x << 8) | (x >> 8);
*ptr=x;
}
void swap32(void * p)
{
uint32_t *ptr=p;
uint32_t x = *ptr;
x = (x << 16) | (x >> 16);
x = ((x & 0x00FF00FF) << 8) | ((x >> 8) & 0x00FF00FF);
*ptr=x;
}
void swap64(void * p)
{
uint64_t *ptr=p;
uint64_t x = *ptr;
x = (x << 32) | (x >> 32);
x = ((x & 0x0000FFFF0000FFFF) << 16) | ((x >> 16) & 0x0000FFFF0000FFFF);
x = ((x & 0x00FF00FF00FF00FF) << 8) | ((x >> 8) & 0x00FF00FF00FF00FF);
*ptr=x;
}