使用 Microchip SAM9X60 OTP 存储板卡的MAC地址和序列号

1. 介绍

SAM9X60 处理器有部分OTP(One Time Programming) Aera 可用于存储user data,这样的话我们就可以将板卡 MAC Address和 SN 序列号写到固定的OTP User Area中。

为什么要使用 OTP 区域存储MAC地址和序列号呢?答案是为了省钱。如果不使用OTP的话,可能要考虑采用 eeprom 等掉电非易失的存储保存这些固定信息,这样的话就多了一颗料的钱,还得占用I2C总线,PCB布线等。

这里提一句,NXP i.MX6系列的处理器,对 OTP 区域的划分比Microchip的处理器要详细和更容易访问一些,没有那么多弯弯绕绕,感兴趣的小伙伴可以看一下i.MX6 Reference Manual.

2. MAC地址和SN 格式

假如格式如下:

3. SAM9X60 OTP Address

SAM9X60 处理器OTP相关的介绍。

Memory Mapping

查看SAM9X60 芯片手册 '23. OTP Memory Controller(OTPC)'章节的相关内容。


每一个Packet包含1个32-bit的Header和若干个32-bit的Payload(data)。

Payload 的 SIZE 自己可以在Header的寄存器中定义。

我们这里要存储1个MAC地址,1个SN序列号的话,为了方便操作和录入,这里就使用4个32bit的payload。Header地址从0x00开始,然后地址0x01 ~ 0x04分别代表payload0~payload4。

3.1. 写操作流程

bash 复制代码
OTPC_MR (0x4)写 0xFF0000   - devmem 0xEFF00004 32 0xFF0000

OTPC_CR (0x0)写 0x40

读 OTPC_ISR (0x1C)查看这个位EOR (bit8)是否是1

读 OTPC_HR (0x20)查看寄存器中的值是否有1,

读 OTPC_DR (0x24)查看寄存器中的值是否有1  (或者读 OTPC_SR (0x0C)寄存器,查看 ONEF bit9 这一位是否为1)

OTPC_MR (0x4)写 0x00000010 ,将OTPC_MR.ADDR 设置为 0,表示从0x0地址开始生成新的packet.这个操作可能触发自动 flush

OTPC_HR (0x20)写 0x301 ,表示我们要使用4个payload, packet类型是 REGULAR 普通的用户数据。

OTPC_AR (0x08)写 0x10000, 表示地址从0x0开始,并且再每次写入数据后,该地址会自动递增,就不用我们再手动增加写入的数据地址了。

OTPC_DR (0x24)写入第一个数据 0x41300001

OTPC_DR (0x24)写入第二个数据 0x00000220

OTPC_DR (0x24)写入第三个数据 0x55508000

OTPC_DR (0x24)写入第四个数据 0x0000001F

OTPC_DR (0x24)写入第五个数据 0x55508001

OTPC_DR (0x24)写入第六个数据 0x0000001F

数据写完之后,OTPC_CR (0x0)写 0x71670001 ,使能programming.

查看写操作是否结束,读 OTPC_ISR (0x1C) EOP bit0是否是1,或者读 OTPC_SR (0x0C) PGM bit0 是否是0

3.2. OTP Lock操作流程

数据写完之后进行Lock,操作如下:

3.3. 读操作流程

bash 复制代码
OTPC_MR (0x4) 寄存器中先写入 header 的地址 ,写入0x0

OTPC_CR.READ (0x0)必须设置为1,OTPC_CR这个寄存器是write-only的,仅仅写入 0x40即可,这样就可以读 user area 了。

等待 OTPC_ISR(0x1C)中 EOR(bit8) 这个位变为1 或者等待 OTPC_SR(0x0C)READ (bit6)这个位变成0,这就意味着整个packet 已经转移到临时寄存器中了

读 OTPC_HR (0x20)头寄存器,读取每一个paylaod word,paylaod word的地址必须已经写入到 OTPC_AR(0x08).DADDR 中。 OTPC_AR.DADDR的地址在读取 OTPC_DR 后会自动递增,所以在连续读取paylaod word数据时,只需要去读 OTPC_DR即可。

4. MAC Address/SN Programming and Read

4.1. Programming

编写的用于板卡 MAC&SN 录入的shell 脚本如下,仅供参考。

bash 复制代码
#!/bin/sh
 
#########################################################
### Programming MAC Address and SN into SAM9X60 OTP
###
### Author: Heat.Huang
### Date:   2022-5-7
#########################################################
 
function usage(){
    echo "Usage:"
    echo ""
    echo "Programming OTP:"
    echo "    $0 22041100001 00:60:2D:0C:84:27"
 
}
 
function prog_mac(){
    # get serial number
    SN=$1
    if [ ${#SN} -ne 11 ]; then
        echo "ERROR: The type of serial number is error."
        usage
        exit 1
    else
        SN_1=0x${SN:3:11}
        SN_2=0x00000${SN:0:3}
        echo "SN_1: $SN_1"
        echo "SN_2: $SN_2"
    fi
 
    # get mac address
    MAC_ADDR=$2
    if [ ${#MAC_ADDR} -ne 17 ]; then
        echo "ERROR:  The format of MAC adddress you program is error."
        usage
        exit 1
    else
        /sbin/ifconfig eth0 down
        /sbin/ifconfig eth0 hw ether "$MAC_ADDR"
        if [ $? -eq 0 ]; then
            echo "MAC address is valid, can be programmed OTP."
            /sbin/ifconfig eth0 up
        else
            echo "MAC address is not valid."
            /sbin/ifconfig eth0 up
            usage
            exit 1
        fi
        echo "Start programming OTP ......"
        MAC_ADDR_1=`echo $MAC_ADDR | awk '{split($0,addr,":");print addr[1]}'`
        MAC_ADDR_2=`echo $MAC_ADDR | awk '{split($0,addr,":");print addr[2]}'`
        MAC_ADDR_3=`echo $MAC_ADDR | awk '{split($0,addr,":");print addr[3]}'`
        MAC_ADDR_4=`echo $MAC_ADDR | awk '{split($0,addr,":");print addr[4]}'`
        MAC_ADDR_5=`echo $MAC_ADDR | awk '{split($0,addr,":");print addr[5]}'`
        MAC_ADDR_6=`echo $MAC_ADDR | awk '{split($0,addr,":");print addr[6]}'`
 
        HW_OCOTP_MAC0=0x${MAC_ADDR_3}${MAC_ADDR_4}${MAC_ADDR_5}${MAC_ADDR_6}
        HW_OCOTP_MAC1=0x0000${MAC_ADDR_1}${MAC_ADDR_2}
        echo "HW_OCOTP_MAC0: $HW_OCOTP_MAC0"
        echo "HW_OCOTP_MAC1: $HW_OCOTP_MAC1"
    fi 
 
    ##
    ## Steps for program OTPC
    ##
 
    ###
    ### Write Access
    ###
    # read RC register
    echo "Read PMC Clock Generator Main Oscillator Register"
    devmem 0xfffffc20
    # enable RC clock
    echo "Enable the main RC in CKGR_MOR register"
    devmem 0xfffffc20 32 0x01370829
    # read RC register again
    echo "Read PMC Clock Generator Main Oscillator Register again"
    devmem 0xfffffc20
 
    # write OTPC_MR.NPCKT to 0
    echo "write OTPC_MR.NPCKT to 0"
    devmem 0xeff00004 32 0x00000000
    # write OTPC_MR.ADDR to its maximum value
    echo "write OTPC_MR.ADDR to its maximum value"
    devmem 0xeff00004 32 0x00ff0000
    # write OTPC_CR.READ to 1 and wait for the read completion
    echo "write OTPC_CR.READ to 1 and wait for the read completion"
    devmem 0xeff00000 32 0x71670040
    sleep 1
 
    echo "check if End of Read (EOR bit8) is 1"
    devmem 0xeff0001c
    echo "check OTPC Header Register"
    devmem 0xeff00020
    echo "check OTPC Data Register if have data of one"
    devmem 0xeff00024
 
    # Write OTPC_MR.ADDR to 0 and set NPCKT
    devmem 0xeff00004 32 0x00000010
    # write the header value in OTPC_HR
    devmem 0xeff00020 32 0x00000301
    # set DADDR to 0
    devmem 0xeff00008 32 0x00010000
    # write serial number into payload0 and payload1
    devmem 0xeff00024 32 $SN_1
    devmem 0xeff00024 32 $SN_2
    # write eth0 mac address into payload2 and payload3
    devmem 0xeff00024 32 $HW_OCOTP_MAC0
    devmem 0xeff00024 32 $HW_OCOTP_MAC1
 
    # after write , enable OTPC programming
    devmem 0xeff00000 32 0x71670001
    sleep 1
    # check if programming is end
    devmem 0xeff0001c
    sleep 1
    # read the address of new generated packet
    val=`devmem 0xeff00004`
    echo "the address of new generated packet: $val"
    val=0x${val:2:4}0000
    # clear OTPC_MR.NPCKT
    devmem 0xeff00004 32 $val
 
    ###
    ### Read data from OTP to check if write ok
    ###
    devmem 0xeff00000 32 0x00000040
    devmem 0xeff0001c
    devmem 0xeff00020
    devmem 0xeff00008 32 0x00000000
    SN_1_OTP=`devmem 0xeff00024`
    echo "SN_1_OTP = $SN_1_OTP"
 
    SN_2_OTP=`devmem 0xeff00024`
    echo "SN_2_OTP = $SN_2_OTP"
 
    HW_OCOTP_MAC0_OTP=`devmem 0xeff00024`
    echo "HW_OCOTP_MAC0_OTP = $HW_OCOTP_MAC0_OTP"
    HW_OCOTP_MAC0_OTP=${HW_OCOTP_MAC0_OTP:2:9}
    # transfer HEX to DEC
    HW_OCOTP_MAC0_OTP=$((16#$HW_OCOTP_MAC0_OTP))
 
    HW_OCOTP_MAC1_OTP=`devmem 0xeff00024`
    echo "HW_OCOTP_MAC1_OTP = $HW_OCOTP_MAC1_OTP"
    HW_OCOTP_MAC1_OTP=${HW_OCOTP_MAC1_OTP:2:9}
    # transfer HEX to DEC
    HW_OCOTP_MAC1_OTP=$((16#$HW_OCOTP_MAC1_OTP))
 
    ## transfer writen value from HEX to DEC
    HW_OCOTP_MAC0=${HW_OCOTP_MAC0:2:9}
    # transfer HEX to DEC
    HW_OCOTP_MAC0=$((16#$HW_OCOTP_MAC0))
    HW_OCOTP_MAC1=${HW_OCOTP_MAC1:2:9}
    # transfer HEX to DEC
    HW_OCOTP_MAC1=$((16#$HW_OCOTP_MAC1))
 
    if [ "$SN_1_OTP" == "$SN_1" ] && [ "$SN_2_OTP" == "$SN_2" ] && [ $HW_OCOTP_MAC0_OTP -eq $HW_OCOTP_MAC0 ] && [ $HW_OCOTP_MAC1_OTP -eq $HW_OCOTP_MAC1 ]; then
        echo "Programming MAC ADDR and serial number succussful."
    else
        echo "Programming OTP FAIL."
    fi
 
    ###
    ### Lock the new generated packet
    ###
    echo "Lock the new generated packet"
    # write the address value of the header of the packet to lock in OTPC_MR.ADDR
    devmem 0xeff00004 32 $val
    # start a read by seeting OTPC_CR.READ and waiting for the read completion indicated by OTPC_ISR.EOR
    devmem 0xeff00000 32 0x00000040
    sleep 1
    # check if End of Read (EOR bit8) is 1
    devmem 0xeff0001c
    # Write 0x7167 in the OTPC_CR.KEY field and '1' in OTPC_CR.CKSGEN
    devmem 0xeff00000 32 0x71670002
    # the end of the lock operation is indicated by OTPC_ISR.EOL='1' and/or OTPC_SR.LOCK='0'
    lock_ret=`devmem 0xeff0001c`
    eol=${lock_ret:9}
    if [ $eol -eq 2 ]; then
        echo "Lock ok."
    else
        echo "Lock fail."
    fi
 
}
 
 
# Entry
if [ $# == 2 ];then
    prog_mac $@
else
    usage
    exit 1
fi
 
exit 0

4.2. Read

又写了一个读取MAC地址和SN的shell脚本 get_Mac.sh,可以用于板子 Linux系统启动后,运行该脚本从OTP相应的Aera中读取MAC address,如果读取失败会随机生成一个MAC地址,然后通过ifconfig eth0 hw ether 命令配置网卡的IP。脚本还会从OTP读取序列号,并写入到文件 /images/data/SN 中。

get_Mac.sh 脚本源码如下。

bash 复制代码
#!/bin/sh
 
 
 
## add by heat
## Steps for set MAC address and get SN from OTPC
##
 
# read RC register
echo "read RC register"
/sbin/devmem 0xfffffc20
# enable RC clock
echo "enable the main RC in CKGR_MOR register"
/sbin/devmem 0xfffffc20 32 0x01370829
# read RC register again
echo "read RC register again"
/sbin/devmem 0xfffffc20
 
# write the address of header into OTPC_MR
/sbin/devmem 0xeff00004 32 0x00000000
# OTPC_CR.READ set to 1 to enable user area
/sbin/devmem 0xeff00000 32 0x00000040
# check OTPC_ISR.EOR if 0 to wait for start reading
/sbin/devmem 0xeff0001c
sleep 1
# read the header of the packet
/sbin/devmem 0xeff00020
# start address of payload
/sbin/devmem 0xeff00008 32 0x00000000
 
# start reading value from OTP
SN_TAIL=`/sbin/devmem 0xeff00024`
SN_HEAD=`/sbin/devmem 0xeff00024`
MAC0_TAIL=`/sbin/devmem 0xeff00024`
MAC0_HEAD=`/sbin/devmem 0xeff00024`
echo "SN_TAIL=$SN_TAIL"
echo "SN_HEAD=$SN_HEAD"
echo "MAC0_TAIL=$MAC0_TAIL"
echo "MAC0_HEAD=$MAC0_HEAD"
 
if [ $MAC0_TAIL == "0x00000000" ] && [ $MAC0_HEAD == "0x00000000" ]; then
    echo "Cannot get correct data from OTP address 0x0, try to read from OTP address 0x29"
    # write the address of header into OTPC_MR
    /sbin/devmem 0xeff00004 32 0x00290000
    # OTPC_CR.READ set to 1 to enable user area
    /sbin/devmem 0xeff00000 32 0x00000040
    # check OTPC_ISR.EOR if 0 to wait for start reading
    /sbin/devmem 0xeff0001c
    sleep 1
    # read the header of the packet
    /sbin/devmem 0xeff00020
    # start address of payload
    /sbin/devmem 0xeff00008 32 0x00000000
 
    # start reading value from OTP
    SN_TAIL=`/sbin/devmem 0xeff00024`
    SN_HEAD=`/sbin/devmem 0xeff00024`
    MAC0_TAIL=`/sbin/devmem 0xeff00024`
    MAC0_HEAD=`/sbin/devmem 0xeff00024`
    echo "SN_TAIL=$SN_TAIL"
    echo "SN_HEAD=$SN_HEAD"
    echo "MAC0_TAIL=$MAC0_TAIL"
    echo "MAC0_HEAD=$MAC0_HEAD"
fi
 
NONE_MAC0=false
NONE_MAC1=false
## analyse eth0 MAC address
if [ $MAC0_TAIL == "0x00000000" ] && [ $MAC0_HEAD == "0x00000000" ]; then
    echo "Cannot get correct MAC address form OTP"
    echo "Use Random MAC Address"
    MAC0_ADDR_RAND=`echo $RANDOM|md5sum`
    MAC0_ADDR_1=00
    MAC0_ADDR_2=1F
    MAC0_ADDR_3=55
    MAC0_ADDR_4=`echo $MAC0_ADDR_RAND|cut -c 1-2`
    MAC0_ADDR_5=`echo $MAC0_ADDR_RAND|cut -c 3-4`
    MAC0_ADDR_6=`echo $MAC0_ADDR_RAND|cut -c 5-6`
else
    case ${#MAC0_TAIL} in
        2)
        MAC0_OPT_SECTOR_TAIL="0x00000000"
        ;;
        3)
        MAC0_OPT_SECTOR_TAIL="0x0000000"`echo $MAC0_TAIL | cut -b 3`
        ;;
        4)
        MAC0_OPT_SECTOR_TAIL="0x000000"`echo $MAC0_TAIL | cut -b 3-4`
        ;;
        5)
        MAC0_OPT_SECTOR_TAIL="0x00000"`echo $MAC0_TAIL | cut -b 3-5`
        ;;
        6)
        MAC0_OPT_SECTOR_TAIL="0x0000"`echo $MAC0_TAIL | cut -b 3-6`
        ;;
        7)
        MAC0_OPT_SECTOR_TAIL="0x000"`echo $MAC0_TAIL | cut -b 3-7`
        ;;
        8)
        MAC0_OPT_SECTOR_TAIL="0x00"`echo $MAC0_TAIL | cut -b 3-8`
        ;;
        9)
        MAC0_OPT_SECTOR_TAIL="0x0"`echo $MAC0_TAIL | cut -b 3-9`
        ;;
        10)
        MAC0_OPT_SECTOR_TAIL=$MAC0_TAIL
        ;;
        *)
        NONE_MAC0=true
        ;;
    esac
    case ${#MAC0_HEAD} in
        2)
        MAC0_OPT_SECTOR_HEAD="0x0000"
        ;;
        3)
        MAC0_OPT_SECTOR_HEAD="0x000"`echo $MAC0_HEAD | cut -b 3`
        ;;
        4)
        MAC0_OPT_SECTOR_HEAD="0x00"`echo $MAC0_HEAD | cut -b 3-4`
        ;;
        5)
        MAC0_OPT_SECTOR_HEAD="0x0"`echo $MAC0_HEAD | cut -b 3-5`
        ;;
        6)
        MAC0_OPT_SECTOR_HEAD="0x0000"`echo $MAC0_HEAD | cut -b 3-6`
        ;;
        7)
        MAC0_OPT_SECTOR_HEAD="0x000"`echo $MAC0_HEAD | cut -b 3-7`
        ;;
        8)
        MAC0_OPT_SECTOR_HEAD="0x00"`echo $MAC0_HEAD | cut -b 3-8`
        ;;
        9)
        MAC0_OPT_SECTOR_HEAD="0x0"`echo $MAC0_HEAD | cut -b 3-9`
        ;;
        10)
        MAC0_OPT_SECTOR_HEAD=$MAC0_HEAD
        ;;
        *)
        NONE_MAC0=true
        ;;
    esac
 
    MAC0_ADDR_1=`echo ${MAC0_OPT_SECTOR_HEAD} | cut -b 7-8`
    MAC0_ADDR_2=`echo ${MAC0_OPT_SECTOR_HEAD} | cut -b 9-10`
    MAC0_ADDR_3=`echo ${MAC0_OPT_SECTOR_TAIL} | cut -b 3-4`
    MAC0_ADDR_4=`echo ${MAC0_OPT_SECTOR_TAIL} | cut -b 5-6`
    MAC0_ADDR_5=`echo ${MAC0_OPT_SECTOR_TAIL} | cut -b 7-8`
    MAC0_ADDR_6=`echo ${MAC0_OPT_SECTOR_TAIL} | cut -b 9-10`
 
    echo "Got correct MAC address form OTP"
 
fi
 
## set eth0 MAC address
echo "eth0 MAC: $MAC0_ADDR_1:$MAC0_ADDR_2:$MAC0_ADDR_3:$MAC0_ADDR_4:$MAC0_ADDR_5:$MAC0_ADDR_6"
/sbin/ifconfig eth0 down
/sbin/ifconfig eth0 hw ether "$MAC0_ADDR_1:$MAC0_ADDR_2:$MAC0_ADDR_3:$MAC0_ADDR_4:$MAC0_ADDR_5:$MAC0_ADDR_6"
/sbin/ifconfig eth0 up
 
## get SN from OTP
if [ $SN_TAIL == "0x00000000" ] && [ $SN_HEAD == "0x00000000" ]; then
    echo "Cannot get correct SN form OTP"
else
    echo "Got correct SN form OTP"
    SN_1=`printf "%08x" ${SN_TAIL}`
    SN_2=`printf "%03x" ${SN_HEAD}`
    echo "SN: ${SN_2}${SN_1}"
    echo "${SN_2}${SN_1}" > /images/data/SN
fi

没有做什么特别的操作,就是操作一堆寄存器。。。

相关推荐
芋头莎莎39 分钟前
STM32 51单片机设计半导体制冷片温控设计
stm32·嵌入式硬件·51单片机
搬砖的小码农_Sky2 小时前
单片机和FPGA有什么区别?
单片机·嵌入式硬件·fpga开发
折途4 小时前
拆解一下用了两年的三十多块的剃须刀
嵌入式硬件
子朔不言4 小时前
[ARM-2D 专题]6.脏矩形定义的宏使用技巧和分析
c语言·arm开发·arm2d·显控开发-新龙微
x2lab4 小时前
国产化 ARM 环境mysql-mariadb 部署
arm开发·mysql·docker·mariadb
电子科技圈4 小时前
Works With线上开发者大会将提供物联网行业深入的专业知识和技能
科技·mcu·物联网·iot
华清远见成都中心5 小时前
物联网学习路线来啦!
物联网·学习
清风fu杨柳5 小时前
centos7 arm版本编译qt5.6.3详细说明
开发语言·arm开发·qt
清风fu杨柳5 小时前
麒麟服务器工作站SP1 arm环境qt5.6.3源码编译
服务器·arm开发·qt
7yewh7 小时前
嵌入式硬件实战提升篇(一)-泰山派RK3566制作多功能小手机
linux·arm开发·驱动开发·嵌入式硬件·物联网·智能手机·硬件架构