In my last PSU project update I mentioned that the software functions I still had to prototype were:
- PWM control of the FAN (Intel FAN control spec)
- Dithering of DAC output
- Temperature reading
I now have the FAN control implemented and am working on the DAC dithering and temperature reading. I’ve started work on the DAC dithering, and I have now managed to convince myself of how that is going to work. Gerry described the general approach quite well in his post here, but writing an actual implementation of a driver to take a 18 bit number and simulate that output on a 16 bit DAC took a bit of thinking. I’ll detail that further in a future post once I have the software done.
I also now have the LM35 temperature sensors, so hopefully prototyping that final outstanding piece of functionality is just a matter of finding the time to read the data sheet and write a driver.
Intel Fan Driver
The Intel Fan driver turned out to be more interesting to write than I initially anticipated. The speed control aspect is pretty straight forward, and simply entails generating a 25kHz PWM output and varying the duty cycle. For my particular fan I found that even with a 0% duty cycle the fan has a minimum speed of 1000 RPM.
The tachometer part of the driver required a bit more thought. The specification dictates that the fan needs to output two pulses per revolution. I initially went down a bit of a blind alley with the idea that I could use the input capture function of the SMT32 Timers to calculate the number of pulses being received per second. That approach quickly became complicated and after some time away from the keyboard and giving it some more thought with a clear head I realised it was as simple as configuring an GPIO input pin to interrupt each pulse, keep a running total of the pulses in the interrupt routine, and then using the Timer to interrupt each second and calculate the actual speed of the fan and reset the current count.
The following photo shows the board running the fan with the oscilloscope displaying the output pulses from the fan:

The current test software simply increments the output speed by 5% every seconds after the board starts up until it reaches 100%. The test program outputs the current fan speed to the UART each loop. The following screenshot shows the terminal output with the fan speed incrementing each loop (and the ADC reading back the current DAC output):

The software is using a fairly standard C driver approach. I have a struct which represents the hardware resources and state that needs to be tracked over time. This is the “fan” object. Then there are several functions that take a pointer to the fan struct and perform the functional operations such as calculating the current speed, or changing the output PWM to set the speed.
/*
* fan.h
*
* Created on: 25/07/2019
* Author: james
*
* The Fan class provides a driver for an Intel 4 wire CPU cooling fan.
* This class depends on two interrupt functions in stm32f0xx_it.c for the GPIO interrupt
* on the FAN_SENSE pin, and the timer interrupt to calculate the fan speed.
* The fan generates 2 pulses per revolution. The GPIO interrupt simply counts the pulses. A
* Timer interrupt every 1000 milliseconds will call the UpdateSpeed function to calculate
* the current fan speed in RPM.
*/
#include "stdint.h"
#include "tim.h"
#ifndef FAN_FAN_H_
#define FAN_FAN_H_
// The state structure for the fan.
typedef struct {
TIM_HandleTypeDef* pwm_handle;
uint32_t pwm_channel;
uint32_t tach_rpm;
uint32_t tach_count;
} fan_t;
HAL_StatusTypeDef Fan_Init(fan_t* fan);
HAL_StatusTypeDef Fan_SetDutyCycle(fan_t* fan, int8_t percentage);
HAL_StatusTypeDef Fan_UpdateSpeed(fan_t* fan);
uint32_t Fan_GetSpeed(fan_t* fan);
#endif /* FAN_FAN_H_ */
I’m using the ST CubeMX software to do all the configuration of the STM32 and generate the framework code. I have read a lot of negative sentiment about the ST HAL libraries and, if I’m being honest, I can be a bit of a purest and so I can understand where this comes from. However, for someone who wants to get a project up and running with a limited amount of time available, the CubeMX software and HAL libraries make implementing functionality significantly quicker than if I was rolling my own firmware using bare bones register programming.
My one gripe though is that it does make your code quite fragmented. A good example of this is the timer configuration in the Fan driver.
When you configure peripherals using CubeMX it groups the configuration code for common peripherals together. You can also choose to have this code separated out into individual header and source files per peripheral. This is obviously better than everything being dumped in main.c, however it’s still not ideal, as when you are writing drivers your hardware configuration ends up being spread across different files with no obvious connection between them.
The only real solution to this appears to using the CubeMX software to do the initial configuration and code generation, but then move / refactor the generated code into your own driver files. Of course at this point you can no longer use the generate code function of CubeMX update the generated configuration. This of course is not ideal, but I think probably preferable to having functionality spread through a number of different source and header files. Time will tell….