One-wire总线使用一根并联总线完成对于多个设备的访问,通过上拉的OD门实现多设备的读写操作,通过ID区别设备,通过CRC5完成数据校验。
常见对于one-wire总线的操作代码主要使用包含基础循环的延时函数实现位读写时序控制,进而实现总线读写(字节读写)。随着MCU主时钟速度不断加速(STM32F4xx主时钟MCLK为168MHz),延时循环次数变得很巨大,产生了以下的问题:
1.浪费了大量的MCU时钟周期做等待。
2.在不同编译器优化级别下,设定的延时计数值常量有可能产生不精确的延时。
3.需要针对时序设定每个状态机的延时参数,参数调整复杂且不稳定。
1.物理连接方法(PCB设计)
2. UART实现位操作的原理
1)总线复位
使用9600-8-N-1的UART配置,发送0XF0并返回0X10~0X90实现总线复位时序。返回其他值标示总线上无设备挂载。
2)总线位读操作
使用115200-8-N-1的UART配置,发送0XFF并返回0XFF(表示读取bit为1)或其他(表示读取为0)实现总线读时序。
1)总线位写操作
使用115200-8-N-1的UART配置,发送0XFF并返回0XFF(表示写入bit为1)或发送0X00并返回0X00(表示写入为0)实现总线位写时序。
3. DMA方式实现One-wire总线读写实现
除了复位操作外,对于one-wire总线的操作通常以1个字节为读写单位,即连续的8个位操作。如果由用户代码实现多位操作,则需要用户代码不断响应UART中断函数,实现对于UART发送、接收数据寄存器的读写。这会导致代码复杂且打断OS系统其它操作。
而采用DMA方式则很容易实现one-wire总线8bit数据的连续读写。
其原理如下:
1.设定一个宽度为byte容量为8的缓冲。UART的TX/RX DMA传输存储器地址都指向此缓冲。
2.对于byte写操作,将要写入的byte通过上述的位写操作将每一bit转换成发送数据byte顺序存入缓冲,启动两个DMA,通过等待RX DMA的完成标志(TC)完成一次写操作。
3.对于byte读操作,将0xFF连续8次存入缓冲,启动两个DMA,通过等待RX DMA完成标志(TC)完成一次读操作,读取的数据通过上述的位读操作,将bit转换成输出的byte数据。
代码说明
以下代码示例中使用STM32F407VG芯片,使用USART2作为操作的UART,其中TX引脚为PA2,RX引脚为PA3。
使用的DMA为DMA1,DMA通道为Channel4,使用的RX Stream为Stream5,使用的TX Stream为Stream6。
1.全局变量及预定义
// If you want to give ticks RTOS, then uncomment
#define OW_GIVE_TICK_RTOS//使用OS的时候,使能此定义可以让线程放弃时间片。
// Return the status of the function
#define OW_OK 0//操作成功
#define OW_ERROR 1//操作失败
#define OW_NO_DEVICE 2//总线上无设备
#define OW_INVALID_BUF 3//缓冲地址非法
#define OW_USART USART2//操作的UART
#define OW_DMA_CH_RX DMA_Channel_4
#define OW_DMA_CH_TX DMA_Channel_4
#define OW_DMA_STREAM_RX DMA1_Stream5在 STM32F4xx 上通过 UART+DMA 实现 One-Wire 总线
#define OW_DMA_STREAM_TX DMA1_Stream6
#define OW_DMA_FLAG_RX DMA_FLAG_TCIF5//RX DMA完成标志
#define OW_DMA_FLAG_TX DMA_FLAG_TCIF6//TX DMA完成标志
#define OW_BIT_0 0x00//读写位为0时
#define OW_BIT_1 0xff//读写位为1时对应缓冲中的数据
/**
* @brief Buffer for Reception / transmission of one-wire
*/
static uint8_t ow_buf[8];//设备私有缓冲,DMA操作地址
2.初始化
1)串口初始化
不同的波特率用于复位时序或者数据位读写时序。
/**
* @brief Init the uart Peripheral as onewire tx/rx timing controller
* @param baudRate 9600 for reset process, 115200 for bit transfer process
*/
static void OW_UartInit(uint32_t baudRate)
{
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = baudRate;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl =
USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_Init(OW_USART, & USART_InitStructure);
}
2) DMA初始化
两个DMA流设备地址,存储器地址,等参数都一致,只有通道和DMA方向不同。(STM32F4xx中DMA通道也是相同的)。
/**
* @brief Init the dma Peripheral as onewire tx/rx timing controller
*/
static void OW_DmaInit(void)
{
DMA_InitTypeDef DMA_InitStructure;
DMA_StructInit(&DMA_InitStructure);
// DMA common config
DMA_InitStructure.DMA_PeripheralBaseAddr =(uint32_t) &(OW_USART->DR);
DMA_InitStructure.DMA_Memory0BaseAddr =(uint32_t) ow_buf;//读写缓冲地址
DMA_InitStructure.DMA_BufferSize = 8;在 STM32F4xx 上通过 UART+DMA 实现 One-Wire 总线
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
// DMA read
DMA_DeInit(OW_DMA_STREAM_RX);
DMA_InitStructure.DMA_Channel = OW_DMA_CH_RX;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//UART到RAM
DMA_Init(OW_DMA_STREAM_RX, &DMA_InitStructure);
// DMA write
DMA_DeInit(OW_DMA_STREAM_TX);
DMA_InitStructure.DMA_Channel = OW_DMA_CH_TX;
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;//RAM到UART
DMA_Init(OW_DMA_STREAM_TX, &DMA_InitStructure);
}
3) One-wire总线驱动初始化
完成时钟IO配置等,注意TX引脚配置为OD PU-UP。这里启动了UART。
uint8_t OW_Init()
{
GPIO_InitTypeDef GPIO_InitStruct;
if (OW_USART == USART2)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;//OD输出
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOA, & GPIO_InitStruct);
/* Connect alternate function */
GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_USART2);
}
OW_UartInit(115200);//默认为准备数据传输
USART_Cmd(OW_USART, ENABLE);
OW_DmaInit();
return OW_OK;
}
3.复位操作
复位one-wire总线,终止之前的读写操作,并返回总线上是否有设备。
/**
* @brief Performs reset and check for the presence of devices on the bus
* @return uint8_t 0 = Is OK or 1 = No device on the bus
*/
uint8_t OW_Reset()
{
uint8_t ow_presence;
OW_UartInit(9600);
// Send the 0xf0 at 9600
USART_ClearFlag(OW_USART, USART_FLAG_TC);
USART_SendData(OW_USART, 0xF0);//复位开始
while (USART_GetFlagStatus(OW_USART, USART_FLAG_TC) == RESET)//等待传输完成
{
#ifdef OW_GIVE_TICK_RTOS
rt_thread_yield();//让出时间片
#else
__NOP();
#endif
}
ow_presence = USART_ReceiveData(OW_USART);//复位完成
OW_UartInit(115200);
return(ow_presence != 0xF0)? OW_OK : OW_NO_DEVICE;//判断总线上是否有设备
}
4.数据传输
1)总线数据与时序数据转换
1.总线数据转换成时序数据
/**
* @brief Convert one byte to a bit array (length = 8). for tx use.
* @param ow_byte bytes to convert
* @param ow_bits a reference to a buffer size of 8 bytes is not reduced
*/
static void OW_toBits(uint8_t ow_byte, uint8_t *ow_bits)
{
uint8_t i;
for (i=0; i> 1;
}
}
2.时序数据转换成总线数据
static uint8_t OW_toByte(uint8_t *ow_bits)
{
uint8_t i;
uint8_t ow_byte =0;
for (i=0; i> 1;
if (*ow_bits == OW_BIT_1)//转换到总线数据
{
ow_byte |= 0x80;
}
ow_bits ++;
}
return ow_byte;
}
2)数据收发
static void OW_BusXfer(void)
{
// Clear flags
USART_ClearFlag(OW_USART, USART_FLAG_RXNE | USART_FLAG_TC);
DMA_ClearFlag(OW_DMA_STREAM_TX, OW_DMA_FLAG_TX);
DMA_ClearFlag(OW_DMA_STREAM_RX, OW_DMA_FLAG_RX);
// Start the loop to send
USART_DMACmd(OW_USART, USART_DMAReq_Tx | USART_DMAReq_Rx, ENABLE);//顺序1:UART DMA
DMA_Cmd(OW_DMA_STREAM_RX, ENABLE);//顺序2:RX DMA
DMA_Cmd(OW_DMA_STREAM_TX, ENABLE);//顺序3:TX DMA
// Wait until dma receive all 8 byte data在 STM32F4xx 上通过 UART+DMA 实现 One-Wire 总线
while (DMA_GetFlagStatus(OW_DMA_STREAM_RX, OW_DMA_FLAG_RX) == RESET)//等待传输完成
{
#ifdef OW_GIVE_TICK_RTOS
rt_thread_yield();//让出时间片
#else
__NOP();
#endif
}
// Disable DMA
DMA_Cmd(OW_DMA_STREAM_RX, DISABLE);//顺序1:RX DMA
DMA_Cmd(OW_DMA_STREAM_TX, DISABLE);//顺序2:TX DMA
USART_DMACmd(OW_USART, USART_DMAReq_Tx | USART_DMAReq_Rx, DISABLE);//顺序3:UART DMA
}
3)数据写
1.写一个字节
void OW_WriteByte(uint8_t data)
{
OW_toBits(data, ow_buf);
OW_BusXfer();
}
2.写多个字节
void OW_WriteBuf(uint8_t *buf, uint8_t length)
{
while (length> 0)
{
OW_WriteByte(*buf);
buf ++;
length --;
}
}
4)数据读
1.读一个字节
uint8_t OW_ReadByte(void)
{
OW_toBits(0xFF, ow_buf);
OW_BusXfer();
return OW_toByte(ow_buf);
}
2.读多个字节
void OW_ReadBuf(uint8_t *buf, uint8_t length)
{
while (length> 0)
{
*buf = OW_ReadByte();
buf ++;
length --;
}
}
代码使用示例
以下代码演示使用上述驱动代码操作一个或者多个DS18B20温度传感器。
1.读取设备ID
从单个one-wire设备读取ID。
注意:由于没有使用CRC,当总线挂载多个设备的时候,此命令检测得到的ID可能不正确。
void OW_ID(void)
{
uint32_t i;
uint8_t buf[8];
OW_Init();
OW_Reset();//总线复位
OW_WriteByte(0x33);//发送命令0x33:读取ROM
OW_ReadBuf(buf, 8);//读取8byte(64bit)
rt_kprintf("Type= %02X ID= ", buf[0]);//byte[0]:设备类别ID
for (i=6;i>0;i--)
rt_kprintf("%02X",buf);//byte[6]-[1]:设备序列号
rt_kprintf(" CRC= %02X", buf[7]);//byte[7]:CRC值
rt_kprintf("\n");
}
2.从一个传感器读取温度
从单个one-wire设备读取ID。
注意:由于没有使用CRC,当总线挂载多个设备的时候,此命令读取得到的温度数据可能不正确。
/**
* @brief Read data from single 18B20 temp sensor
*/
void OW_Get(void)
{
uint8_t buf[2];
int16_t* buf16 = (int16_t*)&buf[0];
OW_Init();
OW_Reset();//总线复位
OW_WriteBuf("\xCC\x44", 2);//发送命令:0xCC跳过ROM;0x44转换温度
/*max convert time = 750ms, here we set delay = 800ms*/
rt_thread_delay((rt_tick_t)(RT_TICK_PER_SECOND *4/5));//<span style="font-size: 9pt;">最高精度下转换时间<span lang="EN-US">