設置 | 登錄 | 註冊

作者共發了3篇帖子。

【程序】STM32使用SPI接口(8位模式)讀取93C46存儲器上的數據

1樓 巨大八爪鱼 2017-2-24 08:24
/* 93C46選默認的16位模式,但SPI總線上每次發送/接收8位數據 */
#include <stm32f10x.h>

#define _BV(n) (1 << (n))

uint8_t id = 0;
uint16_t num = 0;
const uint8_t seg8[] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90};

void delay(void)
{
    uint16_t i;
    for (i = 0; i < 20000; i++);
}

void ser_in(uint8_t data)
{
    uint8_t i;
    for (i = 0; i < 8; i++)
    {
        GPIOB->BRR = GPIO_BRR_BR9; // SCLK=>PB9
        if (data & 0x80)
            GPIOB->BSRR = GPIO_BSRR_BS7; // DIO=>PB7
        else
            GPIOB->BRR = GPIO_BRR_BR7;
        data <<= 1;
        GPIOB->BSRR = GPIO_BSRR_BS9;
    }
}

void par_out(void)
{
    GPIOB->BRR = GPIO_BRR_BR8; // RCLK=>PB8
    GPIOB->BSRR = GPIO_BSRR_BS8;
}

void seg_scan(void)
{
    uint8_t i;
    uint32_t n = num;
    for (i = 0; i <= 4; i++)
    {
        ser_in(seg8[n % 10]);
        ser_in(_BV(i));
        par_out();
        delay();
        n /= 10;
    }
    
    n = id;
    for (i = 6; i <= 7; i++)
    {
        ser_in(seg8[n % 10]);
        ser_in(_BV(i));
        par_out();
        delay();
        n /= 10;
    }
}

uint16_t _93C46_Read(uint8_t addr)
{
    // SPI中我們配置的是CPOL=0, 即SCK的空閒狀態為低電平; CPHA=0, 也就是在SCK的上升沿對數據進行採樣
    // 這裏會產生一個問題: 根據EEPROM手冊的時序圖Figure 2, 雖然發送數據沒有問題, 但接收數據時,  SCK上升沿後需要等待tPD0或tPD1的時間後本位的數據才會出現在DO上
    // 如果上升沿出現時就抓取數據, 那麼讀到的不是本位的數據,而是上一位的數據
    // 因此,我們接收到的數據都是右移了一位之後的數據
    uint16_t data = 0;
    uint16_t temp;
    
    // 開始
    GPIOA->BRR = GPIO_BRR_BR3; // CS=0
    GPIOA->BSRR = GPIO_BSRR_BS3; // CS=1
    
    SPI1->CR1 |= SPI_CR1_SPE; // 啟用SPI
    SPI1->DR = 0xc0 + ((addr >> 1) & 0x1f); // 發送操作碼及地址碼前5位 (1)
    while ((SPI1->SR & SPI_SR_TXE) == 0); // 注意: TXE=1並不代表當前字節發送完畢, 有可能只發送了一兩個字節
    SPI1->DR = (addr << 7) & 0x80; // 送入下次要發送的內容: 地址碼末位 (2)
    while ((SPI1->SR & SPI_SR_RXNE) == 0); // 等待接收數據
    temp = SPI1->DR; // 忽略這次讀取的數據, 因為收到的數據恆為0xff
    // 數據的發送和接收是同時進行的
    // 只有當前字節發送完畢了, RXNE才置位, 而TXE早就置位了(參閱手冊上的Figure 240)
    // RXNE置位表明(1)已發送完畢, 開始發送(2)
    
    while ((SPI1->SR & SPI_SR_TXE) == 0); // TXE置位後才能放入新數據, 此時(2)還未發送完畢
    SPI1->DR = 0x00; // 送入下次要發的內容: 根據器件手冊上的時序圖, 地址發送完畢後應發送0x00, 即DI一直為低電平,不是什麼都不發 (3)
    while ((SPI1->SR & SPI_SR_RXNE) == 0); // 等待(2)發送完畢
    temp = SPI1->DR; // 收到的數據: 最高位為1(從器件發送的高阻態被視為1), 次高位為0(dummy bit, 空白位), 低6位為所讀取數據的第15~10位
    data = (temp & 0x3f) << 10; // 去掉高兩位後送入data變量
    
    while ((SPI1->SR & SPI_SR_TXE) == 0);
    SPI1->DR = 0x00; // 送入最後一次要發送的內容 (4)
    while ((SPI1->SR & SPI_SR_RXNE) == 0); // 等待(3)發送完畢
    temp = SPI1->DR; // 第9~2位數據
    data |= temp << 2;
    
    while ((SPI1->SR & SPI_SR_TXE) == 0);
    while ((SPI1->SR & SPI_SR_RXNE) == 0); // 等待(4)發送完畢
    temp = SPI1->DR;
    data |= temp >> 6;
    
    // 結束
    GPIOA->BRR = GPIO_BRR_BR3; // CS=0
    while (SPI1->SR & SPI_SR_BSY);
    SPI1->CR1 &= ~SPI_CR1_SPE; // 關閉SPI
    delay();
    return data;
}

int main(void)
{
    uint8_t i;
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_SPI1EN;
    
    // CS(1)接PA3, SCK=PA5接SK(2), MISO=PA6接DO(4), MOSI=PA7接DI(3), ORG懸空選16位模式
    // 根據參考手冊RM0008_166頁的Table25,SCK、MOSI應配置為復用推輓輸出(b),而MISO應配置為帶上拉輸入(8)
    GPIOA->CRL = 0xb8b03000;
    GPIOA->BSRR = GPIO_BSRR_BS6; // 帶上拉輸入
    
    // 數碼管動態掃描端口PB7~PB9
    GPIOB->CRH = 0x00000033;
    GPIOB->CRL = 0x30000000;
    
    SPI1->CR1 |= SPI_CR1_MSTR; // 設為主模式
    // SPI1->CR1 &= ~SPI_CR1_DFF; // 每次傳送的數據位數為8位(DFF=0)
    SPI1->CR1 |= SPI_CR1_BR; // BR=111, 選256分頻
    
    // SPI1->CR2 &= ~SPI_CR2_SSOE; // 不使用NSS(=PA4)端口。因為該端口的有效電平是低電平, 而93C46的有效片選信號為高電平
    SPI1->CR1 |= SPI_CR1_SSM; // 使用軟件管理NSS端口,PA4可用作普通I/O口
    SPI1->CR1 |= SPI_CR1_SSI; // 設置NSS的狀態: 已選中
    
    while (1)
    {
        num = _93C46_Read(id);
        for (i = 0; i < 50; i++)
            seg_scan();
        id++;
        if (id > 63)
            id = 0;
    }
}
2樓 巨大八爪鱼 2017-2-24 08:28
本文所用的單片機型號:STM32F103C8T6
存儲器型號:ST93C46
電源電壓:3.2V
3樓 巨大八爪鱼 2017-2-24 09:22
/* 93C46選默認的16位模式,SPI每次也發送16位數據 */
#include <stm32f10x.h>

#define _BV(n) (1 << (n))
#define CS_0 (GPIOA->BRR = GPIO_BRR_BR3)
#define CS_1 (GPIOA->BSRR = GPIO_BSRR_BS3)

uint8_t id = 0;
uint16_t num = 0;
const uint8_t seg8[] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90};

void delay(void)
{
    uint16_t i;
    for (i = 0; i < 20000; i++);
}

void ser_in(uint8_t data)
{
    uint8_t i;
    for (i = 0; i < 8; i++)
    {
        GPIOB->BRR = GPIO_BRR_BR9; // SCLK=>PB9
        if (data & 0x80)
            GPIOB->BSRR = GPIO_BSRR_BS7; // DIO=>PB7
        else
            GPIOB->BRR = GPIO_BRR_BR7;
        data <<= 1;
        GPIOB->BSRR = GPIO_BSRR_BS9;
    }
}

void par_out(void)
{
    GPIOB->BRR = GPIO_BRR_BR8; // RCLK=>PB8
    GPIOB->BSRR = GPIO_BSRR_BS8;
}

void seg_scan(void)
{
    uint8_t i;
    uint32_t n = num;
    for (i = 0; i <= 4; i++)
    {
        ser_in(seg8[n % 10]);
        ser_in(_BV(i));
        par_out();
        delay();
        n /= 10;
    }
    
    n = id;
    for (i = 6; i <= 7; i++)
    {
        ser_in(seg8[n % 10]);
        ser_in(_BV(i));
        par_out();
        delay();
        n /= 10;
    }
}

uint16_t _93C46_Read(uint8_t addr)
{
    // SPI中我們配置的是CPOL=0, 即SCK的空閒狀態為低電平; CPHA=0, 也就是在SCK的上升沿對數據進行採樣
    // 這裏會產生一個問題: 根據EEPROM手冊的時序圖Figure 2, 雖然發送數據沒有問題, 但接收數據時,  SCK上升沿後需要等待tPD0或tPD1的時間後本位的數據才會出現在DO上
    // 如果上升沿出現時就抓取數據, 那麼讀到的不是本位的數據,而是上一位的數據
    // 因此,我們接收到的數據都是右移了一位之後的數據
    uint16_t data = 0;
    uint16_t temp;
    
    // 開始
    CS_0;
    CS_1;
    
    SPI1->CR1 |= SPI_CR1_SPE; // 啟用SPI
    SPI1->DR = 0xc000 | ((addr & 0x3f) << 7); // 發送操作碼(110)、地址碼 (1)
    while ((SPI1->SR & SPI_SR_TXE) == 0); // 注意: TXE=1並不代表當前字節發送完畢, 有可能只發送了一兩個字節
    SPI1->DR = 0x0000; // 送入下次要發的內容: 根據器件手冊上的時序圖, 地址發送完畢後應發送0x0000, 即DI一直為低電平,不是什麼都不發 (2)
    while ((SPI1->SR & SPI_SR_RXNE) == 0); // 等待接收數據
    temp = SPI1->DR; // 收到的數據: 第15~7位全為1(從器件發送的高阻態被視為1), 第6位為0(dummy bit, 空白位), 第5~0位為所讀取數據的第15~10位
    // 數據的發送和接收是同時進行的
    // 只有當前字節發送完畢了, RXNE才置位, 而TXE早就置位了(參閱手冊上的Figure 240)
    // RXNE置位表明(1)已發送完畢, 開始發送(2)
    data = (temp & 0x3f) << 10; // 去掉第15~6位後送入data變量
    
    while ((SPI1->SR & SPI_SR_TXE) == 0);
    while ((SPI1->SR & SPI_SR_RXNE) == 0); // 等待(2)發送完畢
    temp = SPI1->DR; // 第9~0位數據
    data |= temp >> 6;
    
    // 結束
    CS_0;
    while (SPI1->SR & SPI_SR_BSY);
    SPI1->CR1 &= ~SPI_CR1_SPE; // 關閉SPI
    return data;
}

int main(void)
{
    uint8_t i;
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_SPI1EN;
    
    // CS(1)接PA3, SCK=PA5接SK(2), MISO=PA6接DO(4), MOSI=PA7接DI(3), ORG懸空選16位模式
    // 根據參考手冊RM0008_166頁的Table25,SCK、MOSI應配置為復用推輓輸出(b),而MISO應配置為帶上拉輸入(8)
    GPIOA->CRL = 0xb8b03000;
    GPIOA->BSRR = GPIO_BSRR_BS6; // 帶上拉輸入
    
    // 數碼管動態掃描端口PB7~PB9
    GPIOB->CRH = 0x00000033;
    GPIOB->CRL = 0x30000000;
    
    SPI1->CR1 |= SPI_CR1_MSTR; // 設為主模式
    SPI1->CR1 |= SPI_CR1_DFF; // 每次傳送的數據位數為16位(DFF=1)
    SPI1->CR1 |= SPI_CR1_BR; // BR=111, 選256分頻
    
    // SPI1->CR2 &= ~SPI_CR2_SSOE; // 不使用NSS(=PA4)端口。因為該端口的有效電平是低電平, 而93C46的有效片選信號為高電平
    SPI1->CR1 |= SPI_CR1_SSM; // 使用軟件管理NSS端口,PA4可用作普通I/O口
    SPI1->CR1 |= SPI_CR1_SSI; // 設置NSS的狀態: 已選中
    
    while (1)
    {
        num = _93C46_Read(id);
        for (i = 0; i < 50; i++)
            seg_scan();
        id++;
        if (id > 63)
            id = 0;
    }
}

內容轉換:

回覆帖子
內容:
用戶名: 您目前是匿名發表。
驗證碼:
看不清?換一張
©2010-2025 Purasbar Ver3.0 [手機版] [桌面版]
除非另有聲明,本站採用知識共享署名-相同方式共享 3.0 Unported許可協議進行許可。