Blinking LED on STM32F103 with only Linux tools

I decided to do some experiments with STM32.ezgif.com-video-to-gif So I bought cheapest STM32F103C8T6 board for about $5 (ebay). The chip has some nice features, e.g. dozens of GPIOs, AD converter, timers, USB full-speed interface etc., full specs here.

26758547-04eb-4216-8db7-f56666400594The board has JTAG programming/debug interface header so I also needed some JTAG interface. I decided to buy some Chinese j-link clone for about $12 (ebay).

425588861_933When I had the hardware at home, I started to find some know-how on the internet. My requirement was, that the software has to work under Linux.

On the beginning, I’ve found this article, which talks about running STM32F4 discovery under Linux. It gave me some ideas where to start and what should I look for, so I knew I need some example code with makefile, compiler and debug/flash tool.

Installing compiler was the easiest part, the article suggested downloading a package and uncompressing it, but I used another way, simple package installation:

apt-get install gcc-arm-none-eabi

Next step was to get some example code, I searched for blinking LED and after some googling I found this page. With this zip containing sample LED blinking project. It’s not avaiIable now so I reuploaded this file here STM32F103VHB6_RevZ_Demo1_Lanchon_20080210.

dano@u430:~$ unzip STM32F103VHB6_RevZ_Demo1_Lanchon_20080210.zip
Archive:  STM32F103VHB6_RevZ_Demo1_Lanchon_20080210.zip
inflating: STM32F103VHB6_RevZ_Demo1/jtag/flash.cfg
inflating: STM32F103VHB6_RevZ_Demo1/jtag/flash.script
inflating: STM32F103VHB6_RevZ_Demo1/jtag/openocd.cfg
inflating: STM32F103VHB6_RevZ_Demo1/jtag/target.ini
inflating: STM32F103VHB6_RevZ_Demo1/lib/inc/cortexm3_macro.h
inflating: STM32F103VHB6_RevZ_Demo1/lib/inc/stm32f10x_adc.h
inflating: STM32F103VHB6_RevZ_Demo1/lib/inc/stm32f10x_bkp.h
inflating: STM32F103VHB6_RevZ_Demo1/lib/inc/stm32f10x_can.h
inflating: STM32F103VHB6_RevZ_Demo1/lib/inc/stm32f10x_dma.h
inflating: STM32F103VHB6_RevZ_Demo1/lib/inc/stm32f10x_exti.h
inflating: STM32F103VHB6_RevZ_Demo1/lib/inc/stm32f10x_flash.h
inflating: STM32F103VHB6_RevZ_Demo1/lib/inc/stm32f10x_gpio.h
inflating: STM32F103VHB6_RevZ_Demo1/lib/inc/stm32f10x_i2c.h
inflating: STM32F103VHB6_RevZ_Demo1/lib/inc/stm32f10x_iwdg.h
inflating: STM32F103VHB6_RevZ_Demo1/lib/inc/stm32f10x_lib.h
inflating: STM32F103VHB6_RevZ_Demo1/lib/inc/stm32f10x_map.h
inflating: STM32F103VHB6_RevZ_Demo1/lib/inc/stm32f10x_nvic.h
inflating: STM32F103VHB6_RevZ_Demo1/lib/inc/stm32f10x_pwr.h
inflating: STM32F103VHB6_RevZ_Demo1/lib/inc/stm32f10x_rcc.h
inflating: STM32F103VHB6_RevZ_Demo1/lib/inc/stm32f10x_rtc.h
inflating: STM32F103VHB6_RevZ_Demo1/lib/inc/stm32f10x_spi.h
inflating: STM32F103VHB6_RevZ_Demo1/lib/inc/stm32f10x_systick.h
inflating: STM32F103VHB6_RevZ_Demo1/lib/inc/stm32f10x_tim.h
inflating: STM32F103VHB6_RevZ_Demo1/lib/inc/stm32f10x_tim1.h
inflating: STM32F103VHB6_RevZ_Demo1/lib/inc/stm32f10x_type.h
inflating: STM32F103VHB6_RevZ_Demo1/lib/inc/stm32f10x_usart.h
inflating: STM32F103VHB6_RevZ_Demo1/lib/inc/stm32f10x_wwdg.h
inflating: STM32F103VHB6_RevZ_Demo1/lib/src/cortexm3_macro.s
inflating: STM32F103VHB6_RevZ_Demo1/lib/src/stm32f10x_adc.c
inflating: STM32F103VHB6_RevZ_Demo1/lib/src/stm32f10x_bkp.c
inflating: STM32F103VHB6_RevZ_Demo1/lib/src/stm32f10x_can.c
inflating: STM32F103VHB6_RevZ_Demo1/lib/src/stm32f10x_dma.c
inflating: STM32F103VHB6_RevZ_Demo1/lib/src/stm32f10x_exti.c
inflating: STM32F103VHB6_RevZ_Demo1/lib/src/stm32f10x_flash.c
inflating: STM32F103VHB6_RevZ_Demo1/lib/src/stm32f10x_gpio.c
inflating: STM32F103VHB6_RevZ_Demo1/lib/src/stm32f10x_i2c.c
inflating: STM32F103VHB6_RevZ_Demo1/lib/src/stm32f10x_iwdg.c
inflating: STM32F103VHB6_RevZ_Demo1/lib/src/stm32f10x_lib.c
inflating: STM32F103VHB6_RevZ_Demo1/lib/src/stm32f10x_nvic.c
inflating: STM32F103VHB6_RevZ_Demo1/lib/src/stm32f10x_pwr.c
inflating: STM32F103VHB6_RevZ_Demo1/lib/src/stm32f10x_rcc.c
inflating: STM32F103VHB6_RevZ_Demo1/lib/src/stm32f10x_rtc.c
inflating: STM32F103VHB6_RevZ_Demo1/lib/src/stm32f10x_spi.c
inflating: STM32F103VHB6_RevZ_Demo1/lib/src/stm32f10x_systick.c
inflating: STM32F103VHB6_RevZ_Demo1/lib/src/stm32f10x_tim.c
inflating: STM32F103VHB6_RevZ_Demo1/lib/src/stm32f10x_tim1.c
inflating: STM32F103VHB6_RevZ_Demo1/lib/src/stm32f10x_usart.c
inflating: STM32F103VHB6_RevZ_Demo1/lib/src/stm32f10x_vector.c
inflating: STM32F103VHB6_RevZ_Demo1/lib/src/stm32f10x_wwdg.c
inflating: STM32F103VHB6_RevZ_Demo1/lib/STM32_128K_20K_FLASH.ld
inflating: STM32F103VHB6_RevZ_Demo1/lib/STM32_COMMON.ld
inflating: STM32F103VHB6_RevZ_Demo1/lib/STM32_SEC_EXT.ld
inflating: STM32F103VHB6_RevZ_Demo1/lib/STM32_SEC_FLASH.ld
inflating: STM32F103VHB6_RevZ_Demo1/lib/STM32_SEC_RAM.ld
inflating: STM32F103VHB6_RevZ_Demo1/lib/STM32_SEC_RAMonly.ld
extracting: STM32F103VHB6_RevZ_Demo1/lib/version-ld.txt
inflating: STM32F103VHB6_RevZ_Demo1/lib/version-lib.txt
inflating: STM32F103VHB6_RevZ_Demo1/clean-main.c
inflating: STM32F103VHB6_RevZ_Demo1/main.c
inflating: STM32F103VHB6_RevZ_Demo1/makefile
inflating: STM32F103VHB6_RevZ_Demo1/readme.txt
inflating: STM32F103VHB6_RevZ_Demo1/stm32.ld
inflating: STM32F103VHB6_RevZ_Demo1/stm32f10x_conf.h
inflating: STM32F103VHB6_RevZ_Demo1/stm32f10x_it.c
inflating: STM32F103VHB6_RevZ_Demo1/stm32f10x_it.h
dano@u430:~$ cd STM32F103VHB6_RevZ_Demo1/
dano@u430:~/STM32F103VHB6_RevZ_Demo1$ make
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Wall -g -O0 -I . -I lib/inc   -c -o main.o main.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Wall -g -O0 -I . -I lib/inc   -c -o stm32f10x_it.o stm32f10x_it.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Wall -g -O0 -I . -I lib/inc   -c -o lib/src/stm32f10x_adc.o lib/src/stm32f10x_adc.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Wall -g -O0 -I . -I lib/inc   -c -o lib/src/stm32f10x_bkp.o lib/src/stm32f10x_bkp.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Wall -g -O0 -I . -I lib/inc   -c -o lib/src/stm32f10x_can.o lib/src/stm32f10x_can.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Wall -g -O0 -I . -I lib/inc   -c -o lib/src/stm32f10x_dma.o lib/src/stm32f10x_dma.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Wall -g -O0 -I . -I lib/inc   -c -o lib/src/stm32f10x_exti.o lib/src/stm32f10x_exti.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Wall -g -O0 -I . -I lib/inc   -c -o lib/src/stm32f10x_flash.o lib/src/stm32f10x_flash.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Wall -g -O0 -I . -I lib/inc   -c -o lib/src/stm32f10x_gpio.o lib/src/stm32f10x_gpio.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Wall -g -O0 -I . -I lib/inc   -c -o lib/src/stm32f10x_i2c.o lib/src/stm32f10x_i2c.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Wall -g -O0 -I . -I lib/inc   -c -o lib/src/stm32f10x_iwdg.o lib/src/stm32f10x_iwdg.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Wall -g -O0 -I . -I lib/inc   -c -o lib/src/stm32f10x_lib.o lib/src/stm32f10x_lib.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Wall -g -O0 -I . -I lib/inc   -c -o lib/src/stm32f10x_nvic.o lib/src/stm32f10x_nvic.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Wall -g -O0 -I . -I lib/inc   -c -o lib/src/stm32f10x_pwr.o lib/src/stm32f10x_pwr.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Wall -g -O0 -I . -I lib/inc   -c -o lib/src/stm32f10x_rcc.o lib/src/stm32f10x_rcc.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Wall -g -O0 -I . -I lib/inc   -c -o lib/src/stm32f10x_rtc.o lib/src/stm32f10x_rtc.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Wall -g -O0 -I . -I lib/inc   -c -o lib/src/stm32f10x_spi.o lib/src/stm32f10x_spi.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Wall -g -O0 -I . -I lib/inc   -c -o lib/src/stm32f10x_systick.o lib/src/stm32f10x_systick.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Wall -g -O0 -I . -I lib/inc   -c -o lib/src/stm32f10x_tim.o lib/src/stm32f10x_tim.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Wall -g -O0 -I . -I lib/inc   -c -o lib/src/stm32f10x_tim1.o lib/src/stm32f10x_tim1.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Wall -g -O0 -I . -I lib/inc   -c -o lib/src/stm32f10x_usart.o lib/src/stm32f10x_usart.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Wall -g -O0 -I . -I lib/inc   -c -o lib/src/stm32f10x_wwdg.o lib/src/stm32f10x_wwdg.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Wall -g -O0 -c  -o lib/src/cortexm3_macro.o lib/src/cortexm3_macro.s
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Wall -g -O0 -I . -I lib/inc   -c -o lib/src/stm32f10x_vector.o lib/src/stm32f10x_vector.c
lib/src/stm32f10x_vector.c:111:3: warning: taking address of expression of type 'void' [enabled by default]
&_estack,            // The initial stack pointer
^
lib/src/stm32f10x_vector.c:175:1: warning: initialization makes pointer from integer without a cast [enabled by default]
};
^
lib/src/stm32f10x_vector.c:175:1: warning: (near initialization for 'g_pfnVectors[66]') [enabled by default]
arm-none-eabi-ar cr lib/libstm32.a lib/src/stm32f10x_adc.o lib/src/stm32f10x_bkp.o lib/src/stm32f10x_can.o lib/src/stm32f10x_dma.o lib/src/stm32f10x_exti.o lib/src/stm32f10x_flash.o lib/src/stm32f10x_gpio.o lib/src/stm32f10x_i2c.o lib/src/stm32f10x_iwdg.o lib/src/stm32f10x_lib.o lib/src/stm32f10x_nvic.o lib/src/stm32f10x_pwr.o lib/src/stm32f10x_rcc.o lib/src/stm32f10x_rtc.o lib/src/stm32f10x_spi.o lib/src/stm32f10x_systick.o lib/src/stm32f10x_tim.o lib/src/stm32f10x_tim1.o lib/src/stm32f10x_usart.o lib/src/stm32f10x_wwdg.o lib/src/cortexm3_macro.o lib/src/stm32f10x_vector.o
arm-none-eabi-gcc -Wl,--gc-sections,-Map=main.elf.map,-cref,-u,Reset_Handler -I . -I lib/inc -L lib -T stm32.ld main.o stm32f10x_it.o lib/libstm32.a --output main.elf
arm-none-eabi-objcopy -O binary main.elf main.bin

Compilation worked like a charm and at the end I was left with a binary (main.bin) to put into flash.

The code originally used PC1 pin which is not populated in my board so I did a small change into the code to use PB0 and recompiled:

@@ -50,23 +50,23 @@
NVIC_Configuration();

/* Enable GPIOC clock */
-  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
+  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

/* Configure PC.4 as Output push-pull */
-  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
+  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
-  GPIO_Init(GPIOC, &GPIO_InitStructure);
+  GPIO_Init(GPIOB, &GPIO_InitStructure);

while (1)
{
/* Turn on led connected to PC.4 pin */
-    GPIO_SetBits(GPIOC, GPIO_Pin_4);
+    GPIO_SetBits(GPIOB, GPIO_Pin_0);
/* Insert delay */
Delay(0xAFFFF);

/* Turn off led connected to PC.4 pin */
-    GPIO_ResetBits(GPIOC, GPIO_Pin_4);
+    GPIO_ResetBits(GPIOB, GPIO_Pin_0);
/* Insert delay */
Delay(0xAFFFF);
}

Ok, now I needed to flash the binary. So I downloaded original J-Link tools which in turn made my j-link clone unusable (they probably try to fight counterfeits) :(.

[53331.986830] usb 1-3: new full-speed USB device number 53 using xhci_hcd
[53331.987000] usb 1-3: Device not responding to setup address.
[53332.190955] usb 1-3: Device not responding to setup address.
[53332.394579] usb 1-3: device not accepting address 53, error -71
[53332.506592] usb 1-3: new full-speed USB device number 54 using xhci_hcd
[53332.506799] usb 1-3: Device not responding to setup address.
[53332.710690] usb 1-3: Device not responding to setup address.
[53332.914295] usb 1-3: device not accepting address 54, error -71

I didn’t give up and googled how to unbrick it. Fortunately, chip used in j-link is atmel AT91SAM7S64 which supports uploading firmware via USB when put into recovery mode. Full manual is available here. Atmel SAM-BA utility can be also found here: sam-ba_2.12.zip. Using this, I successfully bring my debugger back to life.

Screenshot from 2015-11-15 16:56:19 Screenshot from 2015-11-15 16:56:39So I had to find another utilty for programming. OpenOCD is what I’ve found. Some pages mentioned it as working option so I installed it from repository:

sudo apt-get install openocd

OpenOCD needs a config to know which chip and debugger is used. I easilly found an example here. It mentions ft2232 device as debugger but only a simple change was enough to make it work:

#daemon configuration
telnet_port 4444
gdb_port 3333

#interface
interface jlink

# The chip has 64KB sram
set WORKAREASIZE 0x10000

source [find target/stm32f1x.cfg]
#adapter_khz 100
gdb_breakpoint_override hard
 

#daemon configuration
telnet_port 4444
gdb_port 3333

#interface
interface jlink

# The chip has 64KB sram
set WORKAREASIZE 0x10000

source [find target/stm32f1x.cfg]
#adapter_khz 100
gdb_breakpoint_override hard

After this, my chip was successfully detected:

dano@u430:~/stm32$ openocd -f stm32f103.cfg
Open On-Chip Debugger 0.8.0 (2014-10-25-15:24)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.sourceforge.net/doc/doxygen/bugs.html
Info : only one transport option; autoselect 'jtag'
adapter speed: 1000 kHz
adapter_nsrst_delay: 100
jtag_ntrst_delay: 100
cortex_m reset_config sysresetreq
force hard breakpoints
Info : J-Link initialization started / target CPU reset initiated
Info : J-Link ARM V8 compiled May 27 2009 17:31:22
Info : J-Link caps 0xb9ff7bbf
Info : J-Link hw version 80000
Info : J-Link hw type J-Link
Info : J-Link max mem block 9752
Info : J-Link configuration
Info : USB-Address: 0xff
Info : Kickstart power on JTAG-pin 19: 0xffffffff
Info : Vref = 3.287 TCK = 1 TDI = 0 TDO = 1 TMS = 0 SRST = 0 TRST = 0
Info : J-Link JTAG Interface ready
Info : clock speed 1000 kHz
Info : JTAG tap: stm32f1x.cpu tap/device found: 0x3ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x3)
Info : JTAG tap: stm32f1x.bs tap/device found: 0x16410041 (mfg: 0x020, part: 0x6410, ver: 0x1)
Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints
Error: stm32f1x.cpu -- clearing lockup after double fault
Polling target stm32f1x.cpu failed, GDB will be halted. Polling again in 100ms
Polling target stm32f1x.cpu succeeded again

After some fiddling in debug shell and thanks to this note, I found a way how to successfully flash my binary:

telnet localhost 4444
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Open On-Chip Debugger
> halt
target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0x21000000 pc: 0x080001fc msp: 0x20004fd0
> stm32f1x mass_erase 0
stm32x mass erase complete
> flash erase_check 0
successfully checked erase state
#  0: 0x00000000 (0x400 1kB) erased
#  1: 0x00000400 (0x400 1kB) erased
#  2: 0x00000800 (0x400 1kB) erased
#  3: 0x00000c00 (0x400 1kB) erased
#  4: 0x00001000 (0x400 1kB) erased
#  5: 0x00001400 (0x400 1kB) erased
#  6: 0x00001800 (0x400 1kB) erased
#  7: 0x00001c00 (0x400 1kB) erased
#  8: 0x00002000 (0x400 1kB) erased
#  9: 0x00002400 (0x400 1kB) erased
# 10: 0x00002800 (0x400 1kB) erased
# 11: 0x00002c00 (0x400 1kB) erased
# 12: 0x00003000 (0x400 1kB) erased
# 13: 0x00003400 (0x400 1kB) erased
# 14: 0x00003800 (0x400 1kB) erased
# 15: 0x00003c00 (0x400 1kB) erased
# 16: 0x00004000 (0x400 1kB) erased
# 17: 0x00004400 (0x400 1kB) erased
# 18: 0x00004800 (0x400 1kB) erased
# 19: 0x00004c00 (0x400 1kB) erased
# 20: 0x00005000 (0x400 1kB) erased
# 21: 0x00005400 (0x400 1kB) erased
# 22: 0x00005800 (0x400 1kB) erased
# 23: 0x00005c00 (0x400 1kB) erased
# 24: 0x00006000 (0x400 1kB) erased
# 25: 0x00006400 (0x400 1kB) erased
# 26: 0x00006800 (0x400 1kB) erased
# 27: 0x00006c00 (0x400 1kB) erased
# 28: 0x00007000 (0x400 1kB) erased
# 29: 0x00007400 (0x400 1kB) erased
# 30: 0x00007800 (0x400 1kB) erased
# 31: 0x00007c00 (0x400 1kB) erased
# 32: 0x00008000 (0x400 1kB) erased
# 33: 0x00008400 (0x400 1kB) erased
# 34: 0x00008800 (0x400 1kB) erased
# 35: 0x00008c00 (0x400 1kB) erased
# 36: 0x00009000 (0x400 1kB) erased
# 37: 0x00009400 (0x400 1kB) erased
# 38: 0x00009800 (0x400 1kB) erased
# 39: 0x00009c00 (0x400 1kB) erased
# 40: 0x0000a000 (0x400 1kB) erased
# 41: 0x0000a400 (0x400 1kB) erased
# 42: 0x0000a800 (0x400 1kB) erased
# 43: 0x0000ac00 (0x400 1kB) erased
# 44: 0x0000b000 (0x400 1kB) erased
# 45: 0x0000b400 (0x400 1kB) erased
# 46: 0x0000b800 (0x400 1kB) erased
# 47: 0x0000bc00 (0x400 1kB) erased
# 48: 0x0000c000 (0x400 1kB) erased
# 49: 0x0000c400 (0x400 1kB) erased
# 50: 0x0000c800 (0x400 1kB) erased
# 51: 0x0000cc00 (0x400 1kB) erased
# 52: 0x0000d000 (0x400 1kB) erased
# 53: 0x0000d400 (0x400 1kB) erased
# 54: 0x0000d800 (0x400 1kB) erased
# 55: 0x0000dc00 (0x400 1kB) erased
# 56: 0x0000e000 (0x400 1kB) erased
# 57: 0x0000e400 (0x400 1kB) erased
# 58: 0x0000e800 (0x400 1kB) erased
# 59: 0x0000ec00 (0x400 1kB) erased
# 60: 0x0000f000 (0x400 1kB) erased
# 61: 0x0000f400 (0x400 1kB) erased
# 62: 0x0000f800 (0x400 1kB) erased
# 63: 0x0000fc00 (0x400 1kB) erased
> flash write_bank 0 main.bin 0
wrote 6840 bytes from file main.bin to flash bank 0 at offset 0x00000000 in 0.331746s (20.135 KiB/s)
> reset run
JTAG tap: stm32f1x.cpu tap/device found: 0x3ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x3)
JTAG tap: stm32f1x.bs tap/device found: 0x16410041 (mfg: 0x020, part: 0x6410, ver: 0x1)
>
telnet> q
Connection closed.

And voila! Blinking LED. This whole took me about 5 hours also with this blog writing. Next time I hope I’ll post some more interesting usage of this devel board. Stay tuned.

5 thoughts on “Blinking LED on STM32F103 with only Linux tools”

  1. Hay,
    Do you stil have this zip somewhere? Original page is down and a’m unable to find this zip anywhere.

  2. @Mateusz Wojcik – good to know, maybe paste a link then? Or you just wanted as to enjoy your success on finding it?:>

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.