One-wire Demo on the stm32f4 Discovery Board
Some of the devs at work were struggling to get their software talking to a Dallas 1-wire device. I remember doing 1-wire comms back on the 1990s, but I hadn ' t do any 1-wire lately and all of my old code is for Proces Sors I no longer had running. But I had a weekend free, so I figured I ' d pulling some old 1-wire devices out of the junk bin and write a bit o ' code ...
The 1-wire protocol
The Dallas (now Maxim) 1-wire protocol uses a, a ground wire and a, the is both VDD and data. This was treated by the master and slave devices as a open-collector line. By convention, master and slave Let the line float high, but pull the line low to alert the other device or to drive data On the line. The data line must include a 4.7 kohm resistor to +5 VDC and so the data line idles at a logic high. Note that there is conditions where you might want to switch in a stronger pullup temporarily; If your device requires such a stronger pullup, it'll be called out in the datasheet.
The protocol requires, both master and slave follow a prescribed sequence of operations and adhere to timing constrain Ts. Full details on protocol and timing can is found in any of the Maxim/dallas data sheets for the 1-wire devices.
The master must drive, the data line in Open-drain mode; The master cannot has internal pullups or pulldowns activated!
Interactions between master and slave (device) always begin with the master pulling the data line low for at least 480 use Cs This is called a reset or initialization pulse. The master releases the data line and the Pullup resistor can pull the line high. The device must then respond by pulling, the data line low for the usecs; This is called a presence pulse. After sending the presence pulse, the device releases the data line so it can return high.
After the master sees the presence pulse, the master was free to send a command byte to the device. Details at the vary based on the number and type of devices connected. I don ' t want to get bogged under the details of Multi-drop 1-wire networks, so I'll refer you to the Maxim literature. For this demo, I-m going to focus on a single 1-wire device hooked to the master.
The demo setup
I had a DS1820 temperature sensor in my junk box, so this is the device I used. The DS1820 datasheet on the Maxim website contains all the timing and handshaking info you need to get the device talking to your micro.
For the ' micro ', I chose to use an Stmicros stm32f4 Discovery board. Yes, it is overkill, but it's cheap, kind of hobbyist friendly, and I had one laying around.
I chose to use port pin PC1 for my 1-wire data line. I tied a 3.9 kohm resistor between +5 VDC on the Disco board and PC1. I then connected-a DS1820 by wiring it GND pin to GND on the disco board, it Vdd pin to +5 VDC on the disco board, an D its DQ (data) line to PC1.
Finally, I brought out PD8 and PD9 from the Disco board to a RS-232 level shifter, so I could use TeraTerm to check the R Esults of my program.
The Code
The code for making, all of the, and the DS1820 is pretty straightforward. I ' ve added support for features such as reading the device ' s ROM, which contains the Family Code and serial number, as Wel L as the scratchpad. The Scratchpad is a nine-byte block of RAM that contains the latest conversion information as well as some alarm informati On.
sending 1s and 0s from the master in the 1-wire protocol are simple. The master marks the start of a bit by pulling the data line low. If the bit to send was a 1, the master pulls the data line high between 1 and Usecs later. If the bit to send was a 0, the master keeps the data line low. Between and Usecs after the start of the the bit, the master must ensure the data line was again pulled high. This marks the end of the bit time. Obviously, if the master just sent a 1, the line was already high in the end of this bit time.
This operation was performed a total of eight times to send a single byte. Note that bytes is sent LSB first.
Reading 1s and 0s from the slave is very similar. Again, the master marks the start of a bit by pulling the data line low. The master waits about Usecs, then releases the line and changes the data line to an input. About Usecs later, the master reads the state of the the data line; A high means the slave sent a 1, and a low means the slave sent a 0. The master waits an additional and usecs or so before repeating the process for the next bit, if needed.
Again, this operation was performed a total of eight times to read a single byte. As before, Bytes is sent LSB first.
Commands from the master to the slave follow a set sequence:
- Send the initialization pulse
- Send a command that selects a particular device or all devices on the network
- Send a command plus data OR receive data from the device
- (optionally) Read data from the device
For example, let's say I want to tell the device to start a temperature conversion. That consists of the following steps:
- Send the initialization pulse
- Send a skip_rom command
- Send a convert_temp command
In this case, I "selected" The device by sending a skip_rom command. This tells all 1-wire devices on the network, the everyone is supposed to pay attention. Since There is a device on my network and the DS1820 is selected. The Convert_temp command tells the selected device to begin a termpature conversion. Note that a temperature conversion can take as long as the msecs to complete.
To read the data from this conversion, I need to read the scratchpad, then pull the data from bytes 0 and 1. This uses the following steps:
- Send the initialization pulse
- Send a skip_rom command
- Send a Read_scratchpad command
- Read Nine bytes of data from the device
The above sequence works because I only has a single 1-wire device in My network. If your network has multiple devices, you would need to replace the Skip_rom command with a match_rom command, followed by The 8-byte value for the device, want to select. Details for collecting the ROM values from a network of devices can is found in the Maxim literature.
Once I has the scratchpad with the temperature data, I simply use the values in bytes 0 and 1 to compute the temperature.
Here are the code for setting up the GPIO pin I ' ve selected:
/*
* Specify the port and pin used for 1-wire comms
*/
#define ONEWIRE_PIN_NUM 1
#define ONEWIRE_PIN_MASK (1<<onewire_pin_num)
#define Onewire_port GPIOC
#define ONEWIRE_CLK RCC_AHB1PERIPH_GPIOC
/*
* Onewire_init hardware-specific configuration of 1-wire I/O
*/
static void Onewire_init (void)
{
Gpio_inittypedef gpio_initstructure;
Rcc_ahb1periphclockcmd (ONEWIRE_CLK, ENABLE); Route the Clocks
Gpio_initstructure.gpio_pin = Onewire_pin_mask; Select the PIN to modify
Gpio_initstructure.gpio_mode = Gpio_mode_out; Set the mode to output
Gpio_initstructure.gpio_speed = Gpio_speed_100mhz; Set the I/O speed to
Gpio_initstructure.gpio_otype = Gpio_otype_od; Set the output type to Open-drain
GPIO_INITSTRUCTURE.GPIO_PUPD = Gpio_pupd_nopull; Set the pull-up to None
Gpio_init (Onewire_port, &gpio_initstructure); Do the Init
}
This design lets me define a port and pin by changing the macros at the top, without have to touch the code itself; Less risk of changing all but one hard-coded operations.
Next, I need a set of macros for manipulating the registers associated with my 1-wire data pin. Here they is:
/*
* The following macros Collapse direct accesses of the GPIO registers into
* Single commands. Refer to STM32F4XX_GPIO.C and the stm32f4xx Reference
* Manual (GPIO chapter) for details.
*/
#define Onewire_input_read Onewire_port->idr&onewire_pin_mask
#define Onewire_output_high Onewire_port->bsrrl=onewire_pin_mask
#define Onewire_output_low Onewire_port->bsrrh=onewire_pin_mask
#define Onewire_config_output onewire_port->moder|= (gpio_mode_out<< (onewire_pin_num*2))
#define Onewire_config_input onewire_port->moder&=~ (gpio_moder_moder0<< (onewire_pin_num*2))
These macros let me read from the pin when it's an input, set the "Pin High" or low when it's an output, and configure the P In for either output or input operation.
Now let's look at what's involved in sending an initialization pulse:
static void Sendinitialization (void)
{
Onewire_output_high;
Onewire_config_output;
Delay_usecs (500);
Onewire_output_low;
Delay_usecs (500);
Onewire_output_high;
Onewire_config_input;
Delay_usecs (50);
}
The only interesting bit, this whenever I switch the line from output to input, I need to make sure I first drive t He output high. This was because the Open-collector driver on the PIN would clamp the line low otherwise, preventing the pin from being pull ed up by the external resistor.
The function delay_usecs () does just what it says, delay a given number of microseconds. In my code, I used a spin-loop routine for the delay, but can just as easily set up a timer for generating delays in H Ardware.
Next up was code for sending a byte. That's just a mix of sending 1s and 0s, based on the argument. Here is my routine:
static void Sendbyte (uint8_t val)
{
uint8_t N;
for (n=0; n<8; n++)
{
Onewire_output_low;
Onewire_config_output;
Delay_usecs (5);
if (Val & 1) Onewire_output_high;
Delay_usecs (95);
Onewire_output_high;
Delay_usecs (5);
val = Val >> 1;
}
}
No rocket science here. Bring the line low to start a bit slot, wait 5 usecs, and check the bit to send. If sending a 1, drive the line high, and else leave the line low. Wait for the remaining to microseconds, then ensure the line was high. Rotate the byte to send, repeat until all bits is sent, done.
All that's left at the reading a byte:
Static uint8_t readbyte (void)
{
uint8_t N;
Uint8_t Val;
val = 0;
for (n=0; n<8; n++)
{
val = Val >> 1;
Onewire_output_low;
Onewire_config_output;
Delay_usecs (15);
Onewire_output_high;
Onewire_config_input;
Delay_usecs (10);
if (onewire_input_read) val = val | 0x80
Delay_usecs (35);
}
return Val;
}
In this case, the master needs-to-pull of the line provide a bit slot, and then wait for a moment before releaseing the line. After another short delay, the master samples the data line. If the line was still low, the device is sending a 0. If the line is high, the device is sending a 1. Repeat for eight bits, you had a byte, and you ' re done.
With the low-level stuff out of the the-the-I, I just built up some simple functions for accessing the DS1820. Here's how to read the termperature after a termperature conversion command have completed:
static void Reporttemperature (void)
{
uint32_t Val;
uint32_t T;
uint32_t Frac;
uint8_t N;
Sendinitialization ();
Delay_usecs (100);
Sendbyte (Skip_rom);
Sendbyte (Read_scratchpad);
for (n=0; n<9; n++)
{
Pad[n] = ReadByte ();
}
val = (pad[1] * + pad[0]); Temp in 0.5 degs C
t = val;
val = Val >> 1; Temp in Degs C
Frac = 0;
if (val << 1)! = t) Frac = 5; If the roll lost a bit, allow for 0.5 deg C
xprintf ("\n\rtemperature is:%d.%d degrees C", Val, Frac);
}
This code builds the termperature from bytes 0 and 1 of the scratchpad. This value is the termperature in 0.5 degC increments. My code rolls This value right one bit and then creates a fake floating-point value so the output looks correct. Note that you can use other values in the scratchpad to get finer resolution on the temperature value, if you like; Consult the Maxim docs for details.
Here are a screenshot of my demo program as it appears in TeraTerm:
Here's a picture of my test setup:
As can see, the 1-wire devices is easy-to-use and pretty forgiving.
I have built up networks of these devices spanning dozens of meters, run them down buried bore holes, and never had proble Ms reading data. Communications can is a bit slow, since it takes many milliseconds to perform some of the operations, and if you don ' t nee D speed and want-wiring, the 1-wire family rates a look.
Here's a zip of the source file for this code. Note that you are not being able to rebuild this code as-is, since it uses my custom STM32F4 libraries for UART Comms and Su Chlike. However, I ' ve included the binary image, if you want to add a DS1820 to your Disco board and try off my program.
One-wire Demo on the stm32f4 Discovery Board