I2C EEPROM with error handling on an STM32 microcontroller

Following the post I have written about error handling the I2C communication between an STM32 microcontroller and a TC74 temperature sensor, I have implemented the same rules to talk to an I2C EEPROM, the 24LC01B. However, the same protocol applies to any memory chip using the I2C protocol. The code analysed below has been implemented as a library, in order to use it for various projects. First up, the initialisation of the STM32 I2C peripheral must take place. In the following init function, pins A8 and C9 are used for SCL and SDA respectively withe the I2C3 peripheral, operating at 100kHz. The internal pull-up resistors are used in order to avoid extra components on the board.

void I2C_Memory_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
I2C_InitTypeDef I2C_InitStruct;

RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C3, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);

GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);

GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOC, &GPIO_InitStruct);

GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_I2C3); //SCL
GPIO_PinAFConfig(GPIOC, GPIO_PinSource9, GPIO_AF_I2C3); //SDA

I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStruct.I2C_OwnAddress1 = 0x00;
I2C_InitStruct.I2C_Ack = I2C_Ack_Disable;
I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStruct.I2C_ClockSpeed = 100000;
I2C_DeInit(I2C3);
I2C_Init(I2C3, &I2C_InitStruct);
I2C_Cmd(I2C3, ENABLE); 
}

Next up, the read from memory function is implemented. The I2C_TIMEOUT_MAX is defined as the maximum clocks that the I2C peripheral will wait for response before returning an error and must be defined based on the board layout. After each I2C operation if there is a timeout, the function returns -1. Based on this code, a dedicated I2C protocol error handler function can be implemented.

int I2C_Memory_Read(I2C_TypeDef* I2Cx, uint8_t address)
{
uint32_t timeout = I2C_TIMEOUT_MAX;
uint8_t Data = 0;

I2C_GenerateSTART(I2Cx, ENABLE);

timeout = I2C_TIMEOUT_MAX;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
{
if ((timeout--) == 0) return -1;
}
I2C_Send7bitAddress(I2Cx, 0xA0, I2C_Direction_Transmitter);

timeout = I2C_TIMEOUT_MAX;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
if ((timeout--) == 0) return -1;
} 

I2C_SendData(I2Cx, address);

timeout = I2C_TIMEOUT_MAX;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if ((timeout--) == 0) return -1;
}

I2C_GenerateSTART(I2Cx, ENABLE);
timeout = I2C_TIMEOUT_MAX;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
{
if ((timeout--) == 0) return -1;
}
I2C_Send7bitAddress(I2Cx, 0xA0, I2C_Direction_Receiver);

timeout = I2C_TIMEOUT_MAX;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
{
if ((timeout--) == 0) return -1;
}

I2C_AcknowledgeConfig(I2Cx, DISABLE);

timeout = I2C_TIMEOUT_MAX;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED))
{
if ((timeout--) == 0) return -1;
}

I2C_GenerateSTOP(I2Cx, ENABLE);
Data = I2C_ReceiveData(I2Cx);

return Data; 
}

Finally, the I2C write function is implemented, using the same principles as above. It can be used recursively to send several bytes of data.

int I2C_Memory_Write(I2C_TypeDef* I2Cx, uint8_t address, uint8_t data)
{
uint32_t timeout = I2C_TIMEOUT_MAX;

I2C_GenerateSTART(I2Cx, ENABLE);

timeout = I2C_TIMEOUT_MAX;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
{
if ((timeout--) == 0) return -1;
}
I2C_Send7bitAddress(I2Cx, 0xA0, I2C_Direction_Transmitter);

timeout = I2C_TIMEOUT_MAX;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
if ((timeout--) == 0) return -1;
} 

I2C_SendData(I2Cx, address);

timeout = I2C_TIMEOUT_MAX;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if ((timeout--) == 0) return -1;
}

I2C_SendData(I2Cx, data);

timeout = I2C_TIMEOUT_MAX;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if ((timeout--) == 0) return -1;
}
I2C_GenerateSTOP(I2Cx, ENABLE);

return 0; 
}

The full project to read and write to an EEPROM via I2C with error handling can be found on my Github page.

Teardown and operation of an APC Back-UPS RS 550

In a previous post I showed pictures of an MGE UPS, its circuit schematic and waveforms of its operation. Since I also have an APC UPS of the same era and of equivalent power range, in this post, the same pictures and waveforms will be shown for this one in order to compare the two of them. To begin, the following pictures shows the full UPS. On the top left, the user screen and buttons are connected to the main board. The battery is housed on the bottom left and on the bottom right the huge 50 Hz multiple winding transformer is shown.

The majority of the UPS components on the control board are SMDs. The board houses the power components on the right. Each inverter transistor has its heatsink. Two 40A fuses can be seen, connecter in series to the battery. The filter inductor is shown as well.

 

Similarly to the MGE UPS, there are some relays and filters on the power section. Each cable connected to the board has a ferrite heat shrunk on the cable.

A separate board is used for the telephone line protection.

 The UPS operation is quite similar. In the following figures the transitions when there is an undervoltage is shown.

 

The transition from grid supply to battery supply is shown in the following figures. The battery peak currents during transitions are smaller (about half) compared to the MGE UPS.

Even if the power circuit seams to be similar, because of the different transformer, a small overshoot is observed during the inverter transistor switch off.

 Finally, the output waveform does seem to change, even if the UPS is loaded. For the same load, the MGE UPS had a very different waveform.

Teardown and operation of an MGE NOVA AVR 600

The MGE Nova AVR 600 is a ten year old model used to power a single computer (360W/600VA). In 2007 MGE was purchased by Schneider and merged with APC UPS series. Since I had both an MGE and an APC UPS before they merged, I have the opportunity to show the differences and similarities on their design and their operation.

To begin with the MGE Nova UPS, the schematic of the power stage is shown in the following figure.

The ac grid is connected to the left and the output plug to the computer is on the right. The battery connection is on the bottom and together with its inverter and rectifier charger. The main component of this UPS, as well as on the majority of the UPSs of this power range is the low frequency (50-60Hz) transformer with multiple windings.

The inductance as well as the ohmic resistance for each winding are:

  • L1: 1mH, 100mΩ
  • L2: 1mH, 100mΩ
  • L3: 700mH, 8.8Ω
  • L4: 25mH, 8.5Ω
  • L5: 6.3mH, 300mΩ.

The main power board is shown in the next image. The ethernet connectors on the left are surge protected via MOVs (TVR 07471 - 470V). On the top, the relays S1-S4 switch the power depending on the grid voltage value. The heat sink on the bottom houses four transistors (for the inverter) and one regulator. The microntroller is housed on the right hand side of the board

Two fuses in series with the 12V battery are connected in parallel (40A each).

The switches S1-S4 are shown in detail, together with the low pass filter Lf, Cf, Rf. Cf consists of two 100nF capacitors connected in parallel.

The inverter transistors are the ST P55NF06 (60V, 50A, 16mΩ). Their connected in parallel in pairs. The LM217 on the right is a linear regulator for the auxiliary circuit.

Whereas on the top all the components are through hole, on the bottom their are many SMD components. The high current traces are soldered in order to have lower resistance.

Finally, the MOVs for the surge protection of the main power are not found on the board but are soldered directly on the plugs (not the neatest solution). The MOV type is the TVR 20471 (470V).

The operations that a UPS must do (and will be shown though oscilloscope graphs below) are: normal operation, operation under undervoltage, operation from battery, charging of battery, surge protection. To begin with, the simplest operation is the surge protection, achieved by Z1, Z2 and Z3. Under normal operation, the input be directly connected to the output. This is achieved by having all switches (S1-S4) on. When there is an undervoltage, the UPS does not supply the voltage via the battery. The battery is only employed when the grid is lost. So, this can be done by using an autotransformer formed by L3 and L4. If S2,S3 switch off, then the voltage is stepped up. T0 start with the oscilloscope waveforms, I first tested the UPS operation when an undervoltage occurs. The UPS as shown in the schematic (and as validated by the measurements as well) has a single auxiliary tap to support operation when in undervoltage. So, by using an external autotransformer I began with a voltage input of 230V and slowly decreased it. At 167V S2 and S3 switched off, activating the auxiliary winding L3. So the voltage was stepped up from 167V to 193V which continuous to seem rather low. This operation is shown in the following figure. Notice that the battery charging current (shown in green) is a higher when the auxiliary tap is activated. The opposite action is observed when the input voltage rises. The tap is deactivated when the input voltage reaches 175V, so there is a hysteresis control.

Now in the previous waveform the battery charging current waveform was shown. To elaborate a bit more, the following figure shows the charging current (negative) in detail. The battery is charged with a pulse switching current when the instantaneous grid voltage (and consequently the corresponding voltage of L5) reaches a specific threshold.

To continue with the undervoltage operation, the UPS switches to battery mode when the grid voltage drops bellow 145V, an extremely low value. This transition is shown in the following figure. Before the transition the battery is being charged (negative current). After the transition, the battery supplies the current so the current is positive.

The output voltage of the UPS is far from a sinusoidal wave. However, the principal loads of UPSs are computer power supplies and so a sinusoidal voltage is not a strict requirement. When the transformer of the UPS is loaded, the waveform starts to seem like a sine wave. The battery current is constant.

During startup in battery mode, there is a peak of the buttery current up to 10A for the first cycle, possible to charge the circuit capacitors. The peak is then decreased to about 5A.

The inverter transistor Drain-Source voltage is zero when it is ON, 12V when no transistor is conducting and ~24V when the other transistor is conducting.

Finally, when the grid is again present, the UPS switches from battery supply to grid supply. The threshold to consider that the grid is present is 150V.

Driving a Hard Disk motor

Hard disks spin via a three-phase brushless motor through a driver operating at 12V. Normally, the driver is operated by the hard drive microcontroller, which ensures the quick acceleration at startup and the constant velocity, typically 5400rpm or 7200rpm. Since the motor operates synchronised to the frequency of the pulses, if this frequency is kept constant, then the drive speed is kept constant as well.

I build a drive using an old hdd using an STM32 microcontroller. The hardware is fairly simple. You need a 12V supply (can be a UPS battery or a PC power supply), a full bridge driver (I used a L298), a pulse generator (STM32F429 discovery) and of course the hard drive.

Now the first thing you need to do is to determine the hard drive connections. The motor has 4 cables (three phases and the neutral). The neutral is not needed. To determine which is which, a multimeter can be used. The three phases need to be connected to the output of the full bridge driver. My bridge had 4 outputs (for a stepper motor), I only needed three.

So, the next thing is to generate the pulses to drive the motor. The motor operates synchronously, so the speed is determined by the frequency of the pulses. Therefore, to accelerate the motor, the frequency must increase gradually. The pulses are simple 50% duty cycle pulses, with 120 degrees phase difference.

In the designed program, the controller has a reference speed of 9000rpm and it gradually accelerates the hard drive to reach the reference speed (the nominal speed of the drive is 5400rpm). There are also touch buttons on the screen which will change the reference speed of the drive. So, two timers were used: One (TIM2) to generate the 120degree phase difference pulses and another (TIM3) to control the acceleration of the drive, depending on its speed. In lower speeds, a rapid acceleration will desynchronise the drive. The same applies to very high speeds. A closed loop can be implemented (by optically measuring the drive speed or by measuring the EMF of the winding that is not used), however the system works sufficiently well even without it.

Building a WiFi power switch – The UNIX client

In previous posts the hardware to build a power switch controlled by WiFi, using the ESP8266 module, as well the microcontroller code were depicted. The switch can be controlled via accessing a web URL:

  • "http://<IP address>/gpio/1" turns the switch ON
  • "http://<IP address>/gpio/0" turns the switch OFF
  • "http://<IP address>/gpio/status" displays the status

The aim of the power switch is to control the exterior lighting, so it needs to switch on and off every day. A UNIX system using the cron scheduler seems the perfect implementation. As the system needs to be autonomous, in case the ESP8266 does not respond, the client needs to make several attempts before giving up. In the meantime, it needs to log every failed attempt (using logger) for future reference. The time between each attempt is doubled each time. The DHCP server of the router is configured to give a permanent IP address to the ESP8266 module. The above requirements are embedded in a bash script. The script is used with two arguments, the first is the ip and the second is the state (0, 1 or status).

#!/bin/bash
arguments=$#
max_attempts=10 #maximum attempts to try to connect
timeout=2       #initial timeout in seconds
attempt=0
if [ "$arguments" -ne "2" ] 
then echo "usage: setstate <ip> <state>"
exit 1
fi
dev=$1
state=$2
url="http://"$dev"/gpio/"$state
while (($attempt &amp;lt; $max_attempts)) do curl -s --max-time 1 $url > /dev/null
    exitCode=$?
    if [[ $exitCode == 0 ]]
    then
        break
    fi
     
    logger -p error "[WARNING] Wireless $ip not responding, retrying in $timeout"
    echo "Failure! Retrying in $timeout.." 1>&2
    sleep $timeout
    attempt=$(( attempt + 1 ))
    timeout=$(( timeout * 2 ))
done
if [[ $exitCode != 0 ]]
  then
    logger -p error "[ERROR] Wireless $ip not responding"
    echo "Maximum attempts reached trying to connect ($@)" 1>&2
fi
exit $exitCode

The full project for the ESP8266 power switch can be found on my Github page.