PWM Generator on a STM32L152 Discovery

The STM32L152 Discovery is a compact board which includes an LCD display, a touch sensor (linear or four keys), a push button and two LEDs. Combining all of the above, a compact PWM generator can be made with variable frequency and duty cycle.

The LCD will display the current value to be adjusted meaning either the frequency or duty cycle of the pulse. To cycle between the two settings, the push button will be used. The values will be set using the touch sensor. Finally, the pulse will be output to the green LED in order to show the actual duty cycle (via the LED intensity). The second LED will be used to show which of the two values is currently adjusted (it will also be shown in the display).

So, the timer that will be used is TIM4 and Channel 2, in order to output the PWM pulse to the blue LED (Port PB7). A standard initialization function is first run, meaning starting the timebase, the output channel and finally connecting the channel 2 output to port PB7:

void TIM4Init()
{
TIM_TimeBaseInitTypeDef TIM_InitStruct;
TIM_OCInitTypeDef TIM_OCInitStruct;
GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);
TIM_InitStruct.TIM_ClockDivision = TIM_CKD_DIV1 ;
TIM_InitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_InitStruct.TIM_Period = 32000;
TIM_InitStruct.TIM_Prescaler = 1;
TIM_TimeBaseInit(TIM4, &TIM_InitStruct);
TIM_Cmd(TIM4, ENABLE);

TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_Pulse = 100;
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC2Init(TIM4, &TIM_OCInitStruct);

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);

TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_TIM4);
UpdatePWM(frequency, duty);
}

Next, an easy backend to change the frequency and duty cycle must be used. The function UpdatePWM takes two operands (frequency and duty cycle) and appropriately configures TIM4. Since the L1 microcontroller can run in different clock frequency configurations, the "SystemCoreClock" variable used, which is set in the system source file (assuming that the APB1 Prescaler is 1):

void UpdatePWM(double frequency, double duty)
{
//frequency in Hz, duty 0-100.0
long int ratio;

ratio = (long)SystemCoreClock/frequency;
TIM4->PSC = (int)(ratio/0xFFFF);

ratio = ((long)SystemCoreClock/(TIM4->PSC+1));
TIM4->ARR = (int)(ratio/frequency)-1;

TIM4->CCR2 = (int)((TIM4->ARR+1)*duty)/100-1;
}

To update the LCD Display, the provided function LCD_GLASS_WriteChar is used for each character, whereas, a custom bargraph function is written to update the bargraph as a percentage value:

void UpdateDisplay(void)
{
bargraph((int) duty, 100);
if (state==FREQ) sprintf(displaystring,"f%4.2dk", (int) (10*frequency));
if (state==DUTY) sprintf(displaystring,"d%4.2d%%", (int) (10*duty));
LCD_GLASS_WriteChar((unsigned char *)displaystring + 0, 0, COLUMN_ON, 1);
LCD_GLASS_WriteChar((unsigned char *)displaystring + 1, 0, 0, 2);
LCD_GLASS_WriteChar((unsigned char *)displaystring + 2, 0, 0, 3);
LCD_GLASS_WriteChar((unsigned char *)displaystring + 3, POINT_ON, 0, 4);
LCD_GLASS_WriteChar((unsigned char *)displaystring + 4, 0, 0, 5);
LCD_GLASS_WriteChar((unsigned char *)displaystring + 5, 0, 0, 6);
}
void bargraph(int value, int max)
{
double percentage=(double)value/(double)max;
BAR0_OFF;
BAR1_OFF;
BAR2_OFF;
BAR3_OFF;
if (percentage>0.25) BAR0_ON;
if (percentage>=0.5) BAR1_ON;
if (percentage>=0.75) BAR2_ON;
if (percentage==1) BAR3_ON;
LCD_bar();
}

Finally, the touch sensor value is read using the provided STMTouch driver.


The full project of the PWM Generator for the STM32L152 Discovery can be found on my Github page.

15" Macbook Pro Retina Heat dissipation

Through the last 7 years I have used three unibody 15" Macbook Pro's, with the Early 2015 Retina Model the latest. The evolution of the power saving through the models and case temperature decrease is tremendous. Taking into account that the latest model is even thinner and more powerful, this is a marvelous engineering application.

Macbooks of the last ten years (apart from the fanless 12" model) use fans that feed the hot air to the front of the case, towards the display. This is mandatory, as the logic board is found below the keyboard, and the battery is positioned below the trackpad, as shown in the teardown photo of iFixit.

All 15" models have tradionally used two fans, spinning at ~2000rpm when idle. The optimization level of the heat dissipation for the Retina model, is shown by using a 2160rpm when idle for the left fan and 2000rpm for the right fan. A thermal image of the heat dissipation is shown bellow. The temperature difference between the two fans is clearly shown.

Replacing an iPhone 5S swollen battery

Recently I noticed that my iPhone 5S switched off because of low battery even though it showed that is was about 50%. After doing several reboots and full charge/discharge cycles the issue was not resolved.

To estimate the battery state of charge, usually the power manager circuits measure the battery voltage. I noticed that the iPhone would switch off when under heavy use (first time was when I was using the GPS to drive to an unknown address, not funny). After several hours, it could switch on again without recharge.

As the problem got worse (it switched off at 70%) I saw that the display popped on the left hand side, just above the battery. Indeed, the battery was swollen and pushed the display, making it a curved designed iPhone (not yet implemented, or at least announced by Apple).

So, I ordered a new battery by iFixit, together with the necessary adhesive strips. As, there are numerous youtube videos of Li-Ion batteries exploding, I preferred to switch off the phone for ~10 days, waiting for delivery of the components. Very relaxing and care-free ten days.

With the arrival of the components (and the necessary complementary candy) I opened the phone and to remove the swollen battery. I have replaced batteries on the 3GS and 4 models. The 5S a lot tougher to disassemble. The flex cables are extremely fragile.

Just for curiosity I opened a small hole on the battery, making it swell even more.

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.