Compare commits
5 Commits
87b0243a5b
...
a1137b5d7c
Author | SHA1 | Date | |
---|---|---|---|
a1137b5d7c | |||
b7513fb65e | |||
538d3f4088 | |||
7ad622db70 | |||
d1570fc47a |
|
@ -20,6 +20,8 @@ A list of things to check before putting a board into production. Update this li
|
|||
![Remove order number options](/assets/img/2024-02-18-PCB-manufacturing-ch/order-number.png)
|
||||
- [ ] Make sure expensive items/option parts not meant for assembly are not placed down/autodetected by JLCPCB's parts catalog (you should use the variants system to ensure this cannot happen).
|
||||
- [ ] Check the orientation of each IC and diode in the PCBA list. For each item, make a note that it has been checked/corrected.
|
||||
- [ ] Check if impedance control is needed. Select `JLC04161H-7628` for standard impedance control stackup.\
|
||||
![Impedance stackup](/assets/img/2024-02-18-PCB-manufacturing-ch/stackup.png)
|
||||
|
||||
### Checkout
|
||||
|
||||
|
|
|
@ -9,6 +9,10 @@ tags: [electronics, emmc, embedded] # systems | embedded | rf | microwave | elec
|
|||
|
||||
Summary of my thought process when troubleshooting a board containing eMMC and USB mass storage class device on the STM32L476.
|
||||
|
||||
## If you are using the STM32L476, check out my repository which has a working example: https://github.com/peter-tanner/eMMC-USB-mass-storage-device-STM32L476
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Download Wireshark and the USB pcap plugin.
|
||||
|
||||
TLDR: Minimum configuration you should try for troubleshooting is:
|
||||
|
@ -47,6 +51,8 @@ Commenting out the `HAL_Delay` in the `SDMMC_PowerState_ON` function and the one
|
|||
|
||||
Try changing the clock division to something larger like `4` or greater. Once it is stable try decreasing the divider to maximize speed. When I used lower values, the bus was unstable and I could not retrieve files or perform format operations.
|
||||
|
||||
> **EDIT: It's now working with 4-bit bus width and 0 clock div, not sure what I did to make it work but it works. I think it might be that I increased my `SYSCLK` since when I started the project it was at the default `16 MHz` since it was using the HSI instead of the HSE.**
|
||||
|
||||
```c
|
||||
hmmc1.Init.ClockDiv = 4;
|
||||
```
|
||||
|
@ -61,7 +67,11 @@ The format fails with:
|
|||
|
||||
I guess my board doesn't work in 4-bit mode so I just compromised with 1-bit mode and it worked again.
|
||||
|
||||
## 4. DMA not working on STM32L476RET6 STM32L476
|
||||
> **EDIT: It's now working with 4-bit bus width and 0 clock div, not sure what I did to make it work but it works. I think it might be that I increased my `SYSCLK` since when I started the project it was at the default `16 MHz` since it was using the HSI instead of the HSE.**
|
||||
|
||||
## 4. DMA/Multiple block DMA not working on STM32L476RET6 STM32L476
|
||||
|
||||
Enable "SDMMC hardware flow control" under the SDMMC peripheral.
|
||||
|
||||
Make sure to switch the DMA direction each for RX and TX
|
||||
|
||||
|
@ -69,25 +79,77 @@ Since we are doing bi-directional transfer just use the SDMMC1 DMA request type.
|
|||
|
||||
![dma config for sdmmc1](/assets/img/2024-06-23-Debugging-STM32-USB-/dma_settings.png)
|
||||
|
||||
Also make sure the preemption priority of the USB is higher than the DMA and SDMMC peripherals and to enable the SDMMC global interrupt.
|
||||
The order of preemption priority is crucial, otherwise the transfer will hang. For example `HAL_MMC_GetCardState()`, will be stuck in the `HAL_MMC_CARD_SENDING` state since the interrupt priority is wrong.
|
||||
|
||||
In my case I am using:
|
||||
|
||||
| NVIC interrupt table | Preemption Priority | Sub Priority |
|
||||
| -------------------------------------------------------- | ------------------- | ------------ |
|
||||
| SDMMC1 global interrupt | 0 | 0 |
|
||||
| DMA2 channel4 global interrupt (Use your DMA controller) | 1 | 0 |
|
||||
| USB OTG FS global interrupt | 3 | 0 |
|
||||
| DMA2 channel4 global interrupt (Use your DMA controller) | 0 | 0 |
|
||||
| SDMMC1 global interrupt | 1 | 0 |
|
||||
| USB OTG FS global interrupt | 2 | 0 |
|
||||
|
||||
You may use any priority value but the priorities should in the same order.
|
||||
|
||||
![nvic priority table](/assets/img/2024-06-23-Debugging-STM32-USB-/nvic_settings.png)
|
||||
|
||||
## Performance with DMA
|
||||
## Performance with DMA but 512 packet size
|
||||
|
||||
Kind of terrible but I am using 1-bit bus width since 4-bit isn't working for me.
|
||||
|
||||
I am using the Toshiba `THGBMJG6C1LBAIL` 8G eMMC module since I thought it was cheap on JLCPCB (but see conclusion for my thoughts on eMMC).
|
||||
|
||||
![crystaldiskmark performance](/assets/img/2024-06-23-Debugging-STM32-USB-/crystaldiskmark.png)
|
||||
|
||||
## Sample code for eMMC and USB mass storage class device
|
||||
> **EDIT: It's now working with 4-bit bus width and 0 clock div, not sure what I did to make it work but it works. I think it might be that I increased my `SYSCLK` since when I started the project it was at the default `16 MHz` since it was using the HSI instead of the HSE.**
|
||||
>
|
||||
> This caused a sizeable performance increase! However it's still quite terrible, since the eMMC module I used claims read/write speeds of 45/35 MB/s at 52 MHz in SDR mode at 3.3V. Obviously having a HAL puts overhead and this figure is probably dependant on the block size.
|
||||
>
|
||||
> ![crystaldiskmark performance 4bit](/assets/img/2024-06-23-Debugging-STM32-USB-/crystaldiskmark_fast.png)
|
||||
|
||||
## 5. MMC performance is very bad, slow write speeds
|
||||
|
||||
One last thing to change is the packet size. Larger packet sizes will perform better (at the cost of more memory).
|
||||
|
||||
Change the packet size under the `USB_DEVICE` middleware parameters:
|
||||
|
||||
![configure media packet size](/assets/img/2024-06-23-Debugging-STM32-USB-/packet_size_cubemx.png)
|
||||
|
||||
Alternatively, in `usbd_conf.h`, change:
|
||||
|
||||
```c
|
||||
#define MSC_MEDIA_PACKET 512U
|
||||
```
|
||||
|
||||
to some **multiple of 512**
|
||||
|
||||
For example:
|
||||
|
||||
```c
|
||||
#define MSC_MEDIA_PACKET 32678U // MUST BE A MULTIPLE OF 512
|
||||
```
|
||||
|
||||
You may need to modify your minimum stack/heap size to accomodate for the larger packet size.
|
||||
|
||||
The maximum is `32678` bytes
|
||||
|
||||
The read/write speeds improve on the previous tests, however it is still no where near the limit of the eMMC module. Currently I have not tried anything else to improve the speed, if there is any way then let me know.
|
||||
|
||||
This benchmark is with the largest media packet size (32768), 4 bit wide bus, 0 clock div and DMA.
|
||||
|
||||
![crystaldiskmark benchmark with largest media packet size](/assets/img/2024-06-23-Debugging-STM32-USB-/crystaldiskmark_fast_large_buf.png)
|
||||
|
||||
## Conclusion
|
||||
|
||||
Overall, eMMC is mechanically robust, however from a cost perspective I wouldn't choose this in a future project unless necesary. While it appears to be cheaper than SD cards and holders, the tight tolerances require JLCPCB's 4-wire kelvin testing for the smaller vias (Unless you use 6-layer which has the pad in via for free), and requires X-ray inspection which is another fee. I wouldn't attempt soldering them manually since I've never dealt with BGA so this is a fee I had to accept.
|
||||
|
||||
In my next project I will try out those embedded SD card modules, they come in DFN-8 packages and should be much cheaper than eMMC while offerring high capacity than NOR flash, but they are less widely known and I would not use them in a project which is going into production, only a hobby project.
|
||||
|
||||
I will update this post with more cases as I run into them. Comment on your experiences debugging USB on STM32 and I'll quote them in the article.
|
||||
|
||||
## Sample code for eMMC and USB mass storage class device (`#define BLOCKING` for simple, non-DMA implementation)
|
||||
|
||||
**Adapted from https://github.com/peter-tanner/eMMC-USB-mass-storage-device-STM32L476**
|
||||
|
||||
### `main.c`
|
||||
|
||||
|
@ -105,14 +167,16 @@ static void MX_SDMMC1_MMC_Init(void)
|
|||
hmmc1.Instance = SDMMC1;
|
||||
hmmc1.Init.ClockEdge = SDMMC_CLOCK_EDGE_RISING;
|
||||
hmmc1.Init.ClockBypass = SDMMC_CLOCK_BYPASS_DISABLE;
|
||||
hmmc1.Init.ClockPowerSave = SDMMC_CLOCK_POWER_SAVE_DISABLE;
|
||||
hmmc1.Init.BusWide = SDMMC_BUS_WIDE_1B;
|
||||
hmmc1.Init.HardwareFlowControl = SDMMC_HARDWARE_FLOW_CONTROL_DISABLE;
|
||||
hmmc1.Init.ClockDiv = 2; // MODIFY THIS AS APPROPRIATE
|
||||
hmmc1.Init.ClockPowerSave = SDMMC_CLOCK_POWER_SAVE_ENABLE;
|
||||
hmmc1.Init.BusWide = SDMMC_BUS_WIDE_4B; // MODIFY THIS AS APPRORIATE - CHANGE TO 1 IF STUFF ISN'T WORKING
|
||||
hmmc1.Init.HardwareFlowControl = SDMMC_HARDWARE_FLOW_CONTROL_ENABLE; // ENABLE HARDWARE FLOW CONTROL
|
||||
hmmc1.Init.ClockDiv = 0; // MODIFY THIS AS APPROPRIATE - USE A HIGHER VALUE IF STUFF ISN'T WORKING
|
||||
if (HAL_MMC_Init(&hmmc1) != HAL_OK)
|
||||
{
|
||||
Error_Handler();
|
||||
}
|
||||
|
||||
// ⚠ NOTE: DO NOT FORGET TO MODIFY THIS AS WELL!!!
|
||||
if (HAL_MMC_ConfigWideBusOperation(&hmmc1, SDMMC_BUS_WIDE_1B) != HAL_OK)
|
||||
{
|
||||
Error_Handler();
|
||||
|
@ -124,85 +188,17 @@ static void MX_SDMMC1_MMC_Init(void)
|
|||
|
||||
### `usbd_storage_if.c`
|
||||
|
||||
```c
|
||||
int8_t STORAGE_Init_FS(uint8_t lun)
|
||||
{
|
||||
/* USER CODE BEGIN 2 */
|
||||
// ALREADY INITIALIZED IN `MX_SDMMC1_MMC_Init` FUNCTION.
|
||||
return USBD_OK;
|
||||
/* USER CODE END 2 */
|
||||
}
|
||||
|
||||
int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size)
|
||||
{
|
||||
/* USER CODE BEGIN 3 */
|
||||
HAL_MMC_CardInfoTypeDef card_info;
|
||||
HAL_StatusTypeDef status = HAL_MMC_GetCardInfo(&hmmc1, &card_info);
|
||||
*block_num = card_info.LogBlockNbr - 1;
|
||||
*block_size = card_info.LogBlockSize;
|
||||
return status;
|
||||
/* USER CODE END 3 */
|
||||
}
|
||||
|
||||
int8_t STORAGE_IsReady_FS(uint8_t lun)
|
||||
{
|
||||
/* USER CODE BEGIN 4 */
|
||||
// eMMC IS ALWAYS CONNECTED TO THE BOARD AND IS ALWAYS READY SINCE IT IS
|
||||
// INITIALIZED AT THE START.
|
||||
return USBD_OK;
|
||||
/* USER CODE END 4 */
|
||||
}
|
||||
|
||||
int8_t STORAGE_IsWriteProtected_FS(uint8_t lun)
|
||||
{
|
||||
/* USER CODE BEGIN 5 */
|
||||
// ASSUME eMMC IS NEVER WRITE PROTECTED ON THIS PARTICULAR BOARD
|
||||
// WRITE PROTECT FEATURE IS NOT USED.
|
||||
return USBD_OK;
|
||||
/* USER CODE END 5 */
|
||||
}
|
||||
|
||||
int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
|
||||
{
|
||||
/* USER CODE BEGIN 6 */
|
||||
// TODO: USE DMA TRANSFER LATER
|
||||
int8_t status = HAL_MMC_ReadBlocks(&hmmc1, buf, blk_addr, (uint32_t)blk_len, TIMEOUT);
|
||||
while (HAL_MMC_GetCardState(&hmmc1) != HAL_MMC_CARD_TRANSFER)
|
||||
;
|
||||
return status;
|
||||
|
||||
/* USER CODE END 6 */
|
||||
}
|
||||
|
||||
int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
|
||||
{
|
||||
/* USER CODE BEGIN 7 */
|
||||
// TODO: USE DMA TRANSFER LATER
|
||||
int8_t status = HAL_MMC_WriteBlocks(&hmmc1, buf, blk_addr, (uint32_t)blk_len, TIMEOUT);
|
||||
while (HAL_MMC_GetCardState(&hmmc1) != HAL_MMC_CARD_TRANSFER)
|
||||
;
|
||||
return status;
|
||||
/* USER CODE END 7 */
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Sample code DMA version
|
||||
|
||||
### `usbd_storage_if.c`
|
||||
|
||||
```c
|
||||
volatile uint8_t mmc_rx_done = 1;
|
||||
volatile uint8_t mmc_tx_done = 1;
|
||||
````c
|
||||
volatile uint8_t mmc_transaction_blks_left = 0;
|
||||
|
||||
void HAL_MMC_RxCpltCallback(MMC_HandleTypeDef *hmmc)
|
||||
{
|
||||
mmc_rx_done = 1;
|
||||
mmc_transaction_blks_left = 0;
|
||||
}
|
||||
|
||||
void HAL_MMC_TxCpltCallback(MMC_HandleTypeDef *hmmc)
|
||||
{
|
||||
mmc_tx_done = 1;
|
||||
mmc_transaction_blks_left = 0;
|
||||
}
|
||||
|
||||
// CHANGE DMA DIRECTION
|
||||
|
@ -215,15 +211,14 @@ HAL_StatusTypeDef MMC_DMA_direction(uint32_t direction)
|
|||
return HAL_DMA_Init(&hdma_sdmmc1);
|
||||
}
|
||||
|
||||
//
|
||||
// MORE AUTOGENERATED CODE...
|
||||
// [...]
|
||||
//
|
||||
|
||||
/**
|
||||
* @brief Initializes over USB FS IP
|
||||
* @param lun:
|
||||
* @retval USBD_OK if all operations are OK else USBD_FAIL
|
||||
*/
|
||||
* @brief Initializes over USB FS IP
|
||||
* @param lun:
|
||||
* @retval USBD_OK if all operations are OK else USBD_FAIL
|
||||
*/
|
||||
int8_t STORAGE_Init_FS(uint8_t lun)
|
||||
{
|
||||
/* USER CODE BEGIN 2 */
|
||||
|
@ -233,12 +228,12 @@ int8_t STORAGE_Init_FS(uint8_t lun)
|
|||
}
|
||||
|
||||
/**
|
||||
* @brief .
|
||||
* @param lun: .
|
||||
* @param block_num: .
|
||||
* @param block_size: .
|
||||
* @retval USBD_OK if all operations are OK else USBD_FAIL
|
||||
*/
|
||||
* @brief .
|
||||
* @param lun: .
|
||||
* @param block_num: .
|
||||
* @param block_size: .
|
||||
* @retval USBD_OK if all operations are OK else USBD_FAIL
|
||||
*/
|
||||
int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size)
|
||||
{
|
||||
/* USER CODE BEGIN 3 */
|
||||
|
@ -251,10 +246,10 @@ int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_
|
|||
}
|
||||
|
||||
/**
|
||||
* @brief .
|
||||
* @param lun: .
|
||||
* @retval USBD_OK if all operations are OK else USBD_FAIL
|
||||
*/
|
||||
* @brief .
|
||||
* @param lun: .
|
||||
* @retval USBD_OK if all operations are OK else USBD_FAIL
|
||||
*/
|
||||
int8_t STORAGE_IsReady_FS(uint8_t lun)
|
||||
{
|
||||
/* USER CODE BEGIN 4 */
|
||||
|
@ -265,10 +260,10 @@ int8_t STORAGE_IsReady_FS(uint8_t lun)
|
|||
}
|
||||
|
||||
/**
|
||||
* @brief .
|
||||
* @param lun: .
|
||||
* @retval USBD_OK if all operations are OK else USBD_FAIL
|
||||
*/
|
||||
* @brief .
|
||||
* @param lun: .
|
||||
* @retval USBD_OK if all operations are OK else USBD_FAIL
|
||||
*/
|
||||
int8_t STORAGE_IsWriteProtected_FS(uint8_t lun)
|
||||
{
|
||||
/* USER CODE BEGIN 5 */
|
||||
|
@ -279,20 +274,35 @@ int8_t STORAGE_IsWriteProtected_FS(uint8_t lun)
|
|||
}
|
||||
|
||||
/**
|
||||
* @brief .
|
||||
* @param lun: .
|
||||
* @retval USBD_OK if all operations are OK else USBD_FAIL
|
||||
*/
|
||||
* @brief .
|
||||
* @param lun: .
|
||||
* @retval USBD_OK if all operations are OK else USBD_FAIL
|
||||
*/
|
||||
int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
|
||||
{
|
||||
/* USER CODE BEGIN 6 */
|
||||
mmc_rx_done = 0;
|
||||
hmmc1.ErrorCode = HAL_MMC_ERROR_NONE;
|
||||
while (HAL_MMC_GetCardState(&hmmc1) != HAL_MMC_CARD_TRANSFER)
|
||||
; // FIXME: IMPLEMENT TIMEOUT FAILSAFE
|
||||
#ifdef BLOCKING
|
||||
if (HAL_MMC_ReadBlocks(&hmmc1, buf, blk_addr, blk_len, TIMEOUT) != HAL_OK)
|
||||
return USBD_FAIL;
|
||||
#else
|
||||
mmc_transaction_blks_left = 1;
|
||||
if (MMC_DMA_direction(DMA_PERIPH_TO_MEMORY) != HAL_OK)
|
||||
{
|
||||
mmc_transaction_blks_left = 0;
|
||||
return USBD_FAIL;
|
||||
}
|
||||
if (HAL_MMC_ReadBlocks_DMA(&hmmc1, buf, blk_addr, blk_len) != HAL_OK)
|
||||
{
|
||||
mmc_transaction_blks_left = 0;
|
||||
return USBD_FAIL;
|
||||
while (!mmc_rx_done)
|
||||
}
|
||||
while (mmc_transaction_blks_left)
|
||||
;
|
||||
#endif
|
||||
hmmc1.ErrorCode = HAL_MMC_ERROR_NONE;
|
||||
while (HAL_MMC_GetCardState(&hmmc1) != HAL_MMC_CARD_TRANSFER)
|
||||
; // FIXME: IMPLEMENT TIMEOUT FAILSAFE
|
||||
return USBD_OK;
|
||||
|
@ -300,20 +310,35 @@ int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t bl
|
|||
}
|
||||
|
||||
/**
|
||||
* @brief .
|
||||
* @param lun: .
|
||||
* @retval USBD_OK if all operations are OK else USBD_FAIL
|
||||
*/
|
||||
* @brief .
|
||||
* @param lun: .
|
||||
* @retval USBD_OK if all operations are OK else USBD_FAIL
|
||||
*/
|
||||
int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
|
||||
{
|
||||
/* USER CODE BEGIN 7 */
|
||||
mmc_tx_done = 0;
|
||||
hmmc1.ErrorCode = HAL_MMC_ERROR_NONE;
|
||||
while (HAL_MMC_GetCardState(&hmmc1) != HAL_MMC_CARD_TRANSFER)
|
||||
; // FIXME: IMPLEMENT TIMEOUT FAILSAFE
|
||||
#ifdef BLOCKING
|
||||
if (HAL_MMC_WriteBlocks(&hmmc1, buf, blk_addr, blk_len, TIMEOUT) != HAL_OK)
|
||||
return USBD_FAIL;
|
||||
#else
|
||||
mmc_transaction_blks_left = 1;
|
||||
if (MMC_DMA_direction(DMA_MEMORY_TO_PERIPH) != HAL_OK)
|
||||
{
|
||||
mmc_transaction_blks_left = 0;
|
||||
return USBD_FAIL;
|
||||
}
|
||||
if (HAL_MMC_WriteBlocks_DMA(&hmmc1, buf, blk_addr, blk_len) != HAL_OK)
|
||||
{
|
||||
mmc_transaction_blks_left = 0;
|
||||
return USBD_FAIL;
|
||||
while (!mmc_tx_done)
|
||||
}
|
||||
while (mmc_transaction_blks_left)
|
||||
;
|
||||
#endif
|
||||
hmmc1.ErrorCode = HAL_MMC_ERROR_NONE;
|
||||
while (HAL_MMC_GetCardState(&hmmc1) != HAL_MMC_CARD_TRANSFER)
|
||||
; // FIXME: IMPLEMENT TIMEOUT FAILSAFE
|
||||
return USBD_OK;
|
||||
|
@ -321,18 +346,14 @@ int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t b
|
|||
}
|
||||
|
||||
/**
|
||||
* @brief .
|
||||
* @param None
|
||||
* @retval .
|
||||
*/
|
||||
* @brief .
|
||||
* @param None
|
||||
* @retval .
|
||||
*/
|
||||
int8_t STORAGE_GetMaxLun_FS(void)
|
||||
{
|
||||
/* USER CODE BEGIN 8 */
|
||||
return STORAGE_LUN_NBR - 1;
|
||||
/* USER CODE END 8 */
|
||||
}
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
I will update this post with more cases as I run into them. Comment on your experiences debugging USB on STM32 and I'll quote them in the article.
|
||||
}```
|
||||
````
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
title: Adding the canonical tag to Gitea to prevent duplicate spam
|
||||
author: peter
|
||||
date: 2024-06-29 02:55:28 +0800
|
||||
categories: [SelfHosting] # Blogging | Electronics | Programming | Mechanical
|
||||
tags: [tip] # systems | embedded | rf | microwave | electronics | solidworks | automation
|
||||
# image: assets/img/2024-06-29-Adding-the-canonical/preview.png
|
||||
---
|
||||
|
||||
Gitea doesn't include the canonical tag in their pages, leading to a lot of duplicate pages being indexed by Google. Each page being indexed has a different sorting option, or other superflous parameter.
|
||||
|
||||
> Duplicate without user-selected canonical
|
||||
>
|
||||
> These pages aren't indexed or served on Google
|
||||
|
||||
Not sure if having a lot of these errors affects SEO but I've noticed a lot of my real pages are being discovered but not indexed by Google.
|
||||
|
||||
![Search console page getting spammed with duplicate urls](/assets/img/2024-06-29-Adding-the-canonical/search%20console.png)
|
||||
|
||||
To fix this,
|
||||
|
||||
1. Create a `custom/templates/base/` directory under your Gitea installation. In my case it is in `/var/lib/gitea/custom/templates/base/`.
|
||||
2. CD to the base directory and run `wget https://raw.githubusercontent.com/go-gitea/gitea/main/templates/base/head.tmpl`. Make sure your Gitea is updated to the latest version, or choose the right version instead of the `main` branch.
|
||||
3. Modify `head.tmpl`: Add this line somewhere in the `<head>` section:
|
||||
|
||||
```html
|
||||
<link rel="canonical" href="{{AppUrl}}{{if $.Link}}{{slice $.Link 1}}{{end}}" />
|
||||
```
|
||||
|
||||
4. Restart the gitea service
|
BIN
assets/img/2024-02-18-PCB-manufacturing-ch/stackup.png
Normal file
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 55 KiB |
BIN
assets/img/2024-06-29-Adding-the-canonical/search console.png
Normal file
After Width: | Height: | Size: 41 KiB |
4
post.sh
|
@ -25,8 +25,8 @@ generate_front_matter() {
|
|||
echo "title: $1"
|
||||
echo "author: peter"
|
||||
echo "date: $current_date"
|
||||
echo "categories: [Blogging] # Blogging | Electronics | Programming | Mechanical"
|
||||
echo "tags: [getting started] # systems | embedded | rf | microwave | electronics | solidworks | automation"
|
||||
echo "categories: [Blogging] # Blogging | Electronics | Programming | Mechanical | SelfHosting"
|
||||
echo "tags: [getting started] # systems | embedded | rf | microwave | electronics | solidworks | automation | tip"
|
||||
echo "# image: assets/img/${filename:0:31}/preview.png"
|
||||
echo "---"
|
||||
}
|
||||
|
|