prettyprint

2023年8月31日 星期四

[Raspberry Pi Pico (c-sdk)] Storage: Ep 4. TinyUSB USB Mass Storage, SD/MMC Devices and W25Q Flash Devices

本實驗接續上一篇:

[Raspberry Pi Pico (c-sdk)] Storage: Ep 3. Using SD/MMC Devices and Flash Devices Together ,再加入USB Mass Storage。



實驗內容
  1. 分別對USB Mass Storage, SD/MMC and W25Q Flash進行100K data的write/read所需要的時間比較。
  2. 同時開啟三個檔案分別在USB MSC, SD 與W25Q Flash上,進行讀寫動作。

一、USB MSC host:

主要使用TinyUSB的程式庫,已移植到RP2040上。使用時在主程式的
  • CMakeLists.txt加入:
target_link_libraries(pico_storage 
        tinyusb_host 
        tinyusb_board
        )
  • 呼叫:
    board_init();
    tuh_init(BOARD_TUH_RHPORT);
  • 在main loop 程式中呼叫
    tuh_task(): 
    處理USB queue message
        基本結構如下
        #include "tusb.h"
        void main(void) {
            board_init();
            tuh_init(BOARD_TUH_RHPORT);
            while(1) {
                tuh_task(): 
            }    
        }
  • 至少重寫4個
    TU_ATTR_WEAK                  __attribute__ ((weak))
    的callback function:
    void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len);

    void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len);

    void tuh_msc_mount_cb(uint8_t dev_addr);

    void tuh_msc_umount_cb(uint8_t dev_addr);
二、USB Mass Storage driver說明:
將上述TinyUSB與FatFS檔案系統整合。
在FatFs有五個主要程式:
DSTATUS disk_initialize (BYTE drv);
DSTATUS disk_status (BYTE drv);
DRESULT disk_read (
BYTE drv, /* Physical drive number (0) */
BYTE *buff, /* Pointer to the data buffer to store read data */
LBA_t sector, /* Start sector number (LBA) */
UINT count /* Number of sectors to read (1..128) */
);
DRESULT disk_write (
BYTE drv, /* Physical drive number (0) */
const BYTE *buff, /* Ponter to the data to write */
LBA_t sector, /* Start sector number (LBA) */
UINT count /* Number of sectors to write (1..128) */
);
DRESULT disk_ioctl (
BYTE drv, /* Physical drive number (0) */
BYTE cmd, /* Control command code */
void *buff /* Pointer to the conrtol data */
);
DSTATUS disk_status (BYTE drv);


FRESULT f_mount(FATFS *fs, const TCHAR *path, BYTE opt);
會呼叫
disk_initialize();
因此在此函式中加入
if (!tuh_inited()) {
      board_init();
      tuh_init(BOARD_TUH_RHPORT);
}
使用時不需要在主程式呼叫borad_init() and tuh_init();

在init USB driver時主動呼叫callback function:
void tuh_msc_mount_cb(uint8_t dev_addr)
在此function 中呼叫
tuh_msc_inquiry(dev_addr, lun, &inquiry_resp, inquiry_complete_cb, 0);
取得device sector countsector size以便在disk_ioctl中傳回此參數。
在disk_read()中呼叫:
tuh_msc_read10(pusb_msc->dev_addr, pusb_msc->lun, buff, sector, (uint16_t) count, disk_io_complete, 0);
在disk_write呼叫
tuh_msc_write10(pusb_msc->dev_addr, pusb_msc->lun, buff, sector, (uint16_t) count, disk_io_complete, 0);

其他詳細程式內容附於文章末尾。

三、成果影片



四、程式碼

spi_sdmmc.c, spi_sdmmc.h, W25Q.c and W25Q.h 請參閱前篇內容。
  • usb_msc.c
 #include "pico/stdlib.h"
#include "usb_msc.h"
#include "bsp/board.h"

static usb_msc_t *_pUSBMSC;

static scsi_inquiry_resp_t inquiry_resp;


bool usb_msc_mounted() {
  return _pUSBMSC->usb_mounted;
}

bool inquiry_complete_cb(uint8_t dev_addr, tuh_msc_complete_data_t const * cb_data)
{
  msc_cbw_t const* cbw = cb_data->cbw;
  msc_csw_t const* csw = cb_data->csw;

  if (csw->status != 0)
  {
    printf("Inquiry failed\r\n");
    return false;
  }

  // Print out Vendor ID, Product ID and Rev
  printf("%.8s %.16s rev %.4s\r\n", inquiry_resp.vendor_id, inquiry_resp.product_id, inquiry_resp.product_rev);

  // Get capacity of device
  _pUSBMSC->block_count = tuh_msc_get_block_count(dev_addr, cbw->lun);
  _pUSBMSC->block_size = tuh_msc_get_block_size(dev_addr, cbw->lun);
  
  printf("Disk Size: %lu MB\r\n", _pUSBMSC->block_count / ((1024*1024)/_pUSBMSC->block_size));

  _pUSBMSC->disk_busy=false;
  _pUSBMSC->usb_mounted=true;
  return true;
}

//------------- IMPLEMENTATION -------------//
void tuh_msc_mount_cb(uint8_t dev_addr)
{
    printf("A MassStorage device is mounted\r\n");
    _pUSBMSC->dev_addr = dev_addr;
    _pUSBMSC->lun = 0;
    tuh_msc_inquiry(_pUSBMSC->dev_addr, _pUSBMSC->lun, &inquiry_resp, inquiry_complete_cb, 0);
}

void tuh_msc_umount_cb(uint8_t dev_addr)
{
  printf("A MassStorage device is unmounted\r\n");
  _pUSBMSC->usb_mounted=false;
  f_unmount(USB_MSC_PATH);
}
/////////////

DRESULT usb_msc_initialize(usb_msc_t* pusb_msc) {  
    //printf("usb_msc disk initialize\r\n");
    if (!tuh_inited()) {
      board_init();
      tuh_init(BOARD_TUH_RHPORT);
      _pUSBMSC=pusb_msc;
      _pUSBMSC->disk_busy = true;
      _pUSBMSC->usb_mounted = false;
      absolute_time_t t1=get_absolute_time();
      absolute_time_t t2;
      while (1) {
        tuh_task();
        if (_pUSBMSC->usb_mounted) {
           _pUSBMSC->Stat &= ~STA_NOINIT;
          break;
        }
        t2 = get_absolute_time();
        if (absolute_time_diff_us(t1, t2) > 10000000) {
           _pUSBMSC->Stat = STA_NOINIT;
           break;
        }
      }
    }
    _pUSBMSC->disk_busy = false;
	return _pUSBMSC->Stat;
}

void wait_for_disk_io(usb_msc_t *pusb_msc)
{
  while(pusb_msc->disk_busy)
  {
    tuh_task();
    led_blinking();
  }
  led_blinking_off();
}

bool disk_io_complete(uint8_t dev_addr, tuh_msc_complete_data_t const * cb_data)
{
  
  (void) dev_addr; (void) cb_data;
  _pUSBMSC->disk_busy = false; //////
  _pUSBMSC->Stat = RES_OK;
  return true;
}

DRESULT usb_msc_ioctl(
    BYTE cmd,		/* Control code */
	void *buff,		/* Buffer to send/receive control data */
    usb_msc_t* pusb_msc
) 
{
    DRESULT res = RES_ERROR;

    switch ( cmd )
    {
        case CTRL_SYNC:
        // nothing to do since we do blocking
        return RES_OK;

        case GET_SECTOR_COUNT:
        //*((DWORD*) buff) = (DWORD) tuh_msc_get_block_count(pusb_msc->dev_addr, pusb_msc->lun);
        *((DWORD*) buff) = pusb_msc->block_count;
        return RES_OK;

        case GET_SECTOR_SIZE:
        //*((WORD*) buff) = (WORD) tuh_msc_get_block_size(pusb_msc->dev_addr, pusb_msc->lun);
        *((WORD*) buff) = (WORD) pusb_msc->block_size;
        return RES_OK;

        case GET_BLOCK_SIZE:
        *((DWORD*) buff) = 1;    // erase block size in units of sector size
        return RES_OK;

        default:
        return RES_PARERR;
    }
}

DRESULT usb_msc_disk_write(
	const BYTE *buff,	/* Ponter to the data to write */
	LBA_t sector,		/* Start sector number (LBA) */
	UINT count, 			/* Number of sectors to write (1..128) */
  usb_msc_t *pusb_msc
) 
{
    pusb_msc->disk_busy = true;
    tuh_msc_write10(pusb_msc->dev_addr, pusb_msc->lun, buff, sector, (uint16_t) count, disk_io_complete, 0);
    wait_for_disk_io(pusb_msc);
    return pusb_msc->Stat;
}
DRESULT usb_msc_disk_read(
	BYTE *buff,	/* Ponter to the data to write */
	LBA_t sector,		/* Start sector number (LBA) */
	UINT count, 			/* Number of sectors to write (1..128) */
  usb_msc_t *pusb_msc
) 
{
    pusb_msc->disk_busy = true;
    tuh_msc_read10(pusb_msc->dev_addr, pusb_msc->lun, buff, sector, (uint16_t) count, disk_io_complete, 0);
    wait_for_disk_io(pusb_msc);
    return pusb_msc->Stat;
}
void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len)
{
}
void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len)
{
}
  • usb_msc.h
 #ifndef _USB_MSC_H_
#define _USB_MSC_H_
#include "tusb.h"

#include "ff.h"
#include "diskio.h"
#include "pico_storage.h"


typedef struct {
    uint8_t dev_addr;
    volatile bool disk_busy;
    uint8_t lun;
    uint32_t block_count;
    uint32_t block_size;
    bool     usb_mounted;
    DRESULT     Stat;

} usb_msc_t;

bool usb_msc_mounted();
DRESULT usb_msc_initialize(usb_msc_t* pusb_msc);
void wait_for_disk_io(usb_msc_t *pusb_msc);
bool disk_io_complete(uint8_t dev_addr, tuh_msc_complete_data_t const * cb_data);
DRESULT usb_msc_ioctl(BYTE cmd,	void *buff,	  usb_msc_t* pusb_msc); 
DRESULT usb_msc_disk_write(const BYTE *buff, LBA_t sector,	UINT count,   usb_msc_t *pusb_msc); 
DRESULT usb_msc_disk_read(BYTE *buff, LBA_t sector,	UINT count,   usb_msc_t *pusb_msc); 
#endif
  • glue.c
 #include "stdio.h"
#include "stdlib.h"
#include "ff.h"
#include "diskio.h"
#include "spi_sdmmc.h"
#include "W25Q.h"
#include "usb_msc.h"
#include "hardware/rtc.h"
#include "inttypes.h"
#include "hardware/gpio.h"


#define SDMMC_DRV_0     0
#define W25Q_DRV_1      1
#define USB_MSC_DRV_2   2

sdmmc_data_t *pSDMMC=NULL;
w25q_data_t *pW25Q = NULL;
usb_msc_t    *pUSB_MSC=NULL;

//==================//
DSTATUS disk_initialize (BYTE drv){
    DSTATUS stat;
    switch (drv) {
        case SDMMC_DRV_0:
            if (pSDMMC == NULL) {
                pSDMMC = (sdmmc_data_t*)malloc(sizeof(sdmmc_data_t));
                pSDMMC->csPin = SDMMC_PIN_CS;
                pSDMMC->spiPort = SDMMC_SPI_PORT;
                pSDMMC->spiInit=false;
                pSDMMC->sectSize=512;
#ifdef __SPI_SDMMC_DMA
                pSDMMC->dmaInit=false;
#endif
    }
            stat = sdmmc_disk_initialize(pSDMMC);
            return stat;
        break;
        case W25Q_DRV_1:
        if (pW25Q == NULL) {
            pW25Q = (w25q_data_t*)malloc(sizeof(w25q_data_t));
            pW25Q->spiInit=false;
            pW25Q->Stat=STA_NOINIT;
        }
		stat = w25q_disk_initialize(W25Q_SPI_PORT, W25Q_PIN_CS, pW25Q); 
		return stat;
		
	    break;
        case USB_MSC_DRV_2:
        if (pUSB_MSC == NULL) {
            pUSB_MSC = (usb_msc_t*)malloc(sizeof(usb_msc_t));
        }
        stat = usb_msc_initialize(pUSB_MSC);
        return stat;
        break;
    }
    return STA_NOINIT;
 }
/*-----------------------------------------------------------------------*/
/* Get disk status                                                       */
/*-----------------------------------------------------------------------*/
DSTATUS disk_status (BYTE drv) {
    DSTATUS stat;
    switch (drv) {
        case SDMMC_DRV_0:
            stat=  sdmmc_disk_status(pSDMMC); /* Return disk status */
            return stat;
        break;
        case W25Q_DRV_1:
            stat = pW25Q->Stat;
            return stat;
        break;
        case USB_MSC_DRV_2:
            //uint8_t dev_addr = pdrv + 1;
            return tuh_msc_mounted(pUSB_MSC->dev_addr) ? 0 : STA_NODISK;
        break;
    }
    return RES_PARERR;
	
}

/*-----------------------------------------------------------------------*/
/* Read sector(s)                                                        */
/*-----------------------------------------------------------------------*/
DRESULT disk_read (
	BYTE drv,		/* Physical drive number (0) */
	BYTE *buff,		/* Pointer to the data buffer to store read data */
	LBA_t sector,	/* Start sector number (LBA) */
	UINT count		/* Number of sectors to read (1..128) */
)
{
    DSTATUS stat;
    switch (drv) {
        case SDMMC_DRV_0:
            stat = sdmmc_disk_read(buff, sector, count, pSDMMC);
            return stat;
        break;
        case W25Q_DRV_1:
            if (pW25Q->Stat & STA_NOINIT) return RES_NOTRDY;
		    w25q_read_sector((uint32_t)sector, 0, buff, count*pW25Q->sectorSize, pW25Q);
            return pW25Q->Stat;
        break;
        case USB_MSC_DRV_2:
            stat = usb_msc_disk_read(buff, sector, count, pUSB_MSC);
            return stat;
        break;
    }
	return RES_PARERR;
}

/*-----------------------------------------------------------------------*/
/* Write sector(s)                                                       */
/*-----------------------------------------------------------------------*/
#if FF_FS_READONLY == 0
DRESULT disk_write (
	BYTE drv,			/* Physical drive number (0) */
	const BYTE *buff,	/* Ponter to the data to write */
	LBA_t sector,		/* Start sector number (LBA) */
	UINT count			/* Number of sectors to write (1..128) */
)
{
    DSTATUS stat = STA_NODISK;
    switch (drv) {
        case SDMMC_DRV_0:
            stat = sdmmc_disk_write(buff, sector, count, pSDMMC);
            return stat;
        break;
        case W25Q_DRV_1:
            stat = w25q_disk_write(buff, sector, count, pW25Q);
            return stat;
        break;
        case USB_MSC_DRV_2:
            stat = usb_msc_disk_write(buff, sector, count, pUSB_MSC);
            return stat;
        break;
    }
	return RES_PARERR;

}
#endif


/*-----------------------------------------------------------------------*/
/* Miscellaneous drive controls other than data read/write               */
/*-----------------------------------------------------------------------*/

DRESULT disk_ioctl (
	BYTE drv,		/* Physical drive number (0) */
	BYTE cmd,		/* Control command code */
	void *buff		/* Pointer to the conrtol data */
)
{
    DSTATUS stat;
    switch (drv) {
        case SDMMC_DRV_0:
            stat = sdmmc_disk_ioctl(cmd, buff, pSDMMC);
            return stat;
        break;
        case W25Q_DRV_1:
            stat = w25q_disk_ioctl(cmd, buff, pW25Q);
            return stat;
        break;
        case USB_MSC_DRV_2:
            stat = usb_msc_ioctl(cmd, buff, pUSB_MSC);
            return stat;
        break;
    }
	return RES_PARERR;
}

DWORD get_fattime(void) {
    datetime_t t = {0, 0, 0, 0, 0, 0, 0};
    bool rc = rtc_get_datetime(&t);
    if (!rc) return 0;

    DWORD fattime = 0;
    // bit31:25
    // Year origin from the 1980 (0..127, e.g. 37 for 2017)
    uint8_t yr = t.year - 1980;
    fattime |= (0b01111111 & yr) << 25;
    // bit24:21
    // Month (1..12)
    uint8_t mo = t.month;
    fattime |= (0b00001111 & mo) << 21;
    // bit20:16
    // Day of the month (1..31)
    uint8_t da = t.day;
    fattime |= (0b00011111 & da) << 16;
    // bit15:11
    // Hour (0..23)
    uint8_t hr = t.hour;
    fattime |= (0b00011111 & hr) << 11;
    // bit10:5
    // Minute (0..59)
    uint8_t mi = t.min;
    fattime |= (0b00111111 & mi) << 5;
    // bit4:0
    // Second / 2 (0..29, e.g. 25 for 50)
    uint8_t sd = t.sec / 2;
    fattime |= (0b00011111 & sd);
    return fattime;
}

void led_blinking(void)
{
    static absolute_time_t  t1;
    static bool state=false;
        
    // Blink every interval ms
    if ( absolute_time_diff_us(t1, get_absolute_time()) < 100000) return; // not enough time
    t1 = get_absolute_time();
    gpio_put(LED_BLINKING_PIN, state);
    state = !state;
}

void led_blinking_off(void) {
    gpio_put(LED_BLINKING_PIN, false);
}
  • pic_storage.h
 #ifndef _PICO_STORAGE_H_
#define _PICO_STORAGE_H_
/* used by my project */
#define SDMMC_PATH      "0:"
#define W25Q_PATH       "1:"
#define USB_MSC_PATH	"2:"
#define SPI_BAUDRATE_LOW (1000*1000)
#define SPI_BAUDRATE_HIGH (50*1000*1000)
/* =================================  */
#define  LED_BLINKING_PIN     25
void led_blinking(void);
void led_blinking_off(void);
/* =================================  */
#endif
  • CMakeLists.txt
 add_library(pico_storage_drv INTERFACE)
target_sources(pico_storage_drv INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/glue.c
    ${CMAKE_CURRENT_LIST_DIR}/FatFs/ff.c
    ${CMAKE_CURRENT_LIST_DIR}/FatFs/ffunicode.c
    ${CMAKE_CURRENT_LIST_DIR}/FatFs/ffsystem.c
    ${CMAKE_CURRENT_LIST_DIR}/sdmmc/spi_sdmmc.c
    ${CMAKE_CURRENT_LIST_DIR}/flash/W25Q.c
    ${CMAKE_CURRENT_LIST_DIR}/usb_msc/usb_msc.c
)

target_include_directories(pico_storage_drv INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}
    ${CMAKE_CURRENT_LIST_DIR}/FatFs
    ${CMAKE_CURRENT_LIST_DIR}/flash
    ${CMAKE_CURRENT_LIST_DIR}/sdmmc
    ${CMAKE_CURRENT_LIST_DIR}/usb_msc
)

target_link_libraries(pico_storage_drv INTERFACE
        hardware_spi
        hardware_dma
        hardware_rtc
        pico_stdlib
        tinyusb_board
        tinyusb_host
)

2023年8月24日 星期四

PWM Ep.2: Raspberry Pi Pico Remotely Controls Servo Motors Using ADC and nRF24L01+

本篇文章說明以PWM控制Servo Motor。以Raspberry Pi Pico整合ADC, nRF24L01+遠端遙控ESP32-CAM鏡頭拍攝的角度。

一、Servo motor and PWM:

上圖說明Servo的PWM Period為20ms(50Hz),Pico-SDK以下列指令來設定:

    gpio_set_function(pin, GPIO_FUNC_PWM);
    slice = pwm_gpio_to_slice_num(pin);
    pwm_ch = pwm_gpio_to_channel(pin);
    pwm_config c = pwm_get_default_config();
    pwm_config_set_clkdiv(&c, 125);  // 20ms period, steps 20000, clkdiv = 125 
    pwm_config_set_wrap(&c, 20000);
    pwm_config_set_phase_correct(&c, false);
    pwm_init(*slice, &c, true);  

系統clock為125M Hz
125M Hz/125/20000 = 50Hz。

以下列指令設定PWM duty Cycle:

pwm_set_chan_level(slice, pwm_ch, top_count);

top_count:angle*1000/180+15000, 0度時1.5ms duty cycle。-90~90為1ms~2ms。

實際使用時可能因為所使用元件誤差,需要調整,在本篇文章的實驗時-90~90為0.5ms~2.5ms。

二、ADC:

Raspberry Pi Pico 共有4個ADC ports,如上圖所示。解析度為12 bit因此取樣值為0~4095。下列指令

adc_init();
adc_gpio_init(ADC_PIN); // init and assign ADC pin

adc_select_input(ADC_NUM);  // gpio 26: adc_num 0, gpio 27:1,  gpio 28 : 2
adv_value = adc_read();

三、nRF24L01+:

驅動程式請參閱[Raspberry Pi Pico] nRF24L01+ Ep. 1 : Pico-SDK C-code driver using IRQ,相關網路拓撲請參閱[Raspberry Pi Pico] nRF24L01+ Ep. 2 : Various types of network topologies 

  • 發送端設定

nRF24_spi_default_init(20, 21, nRF24_irq_callback);
nRF24_config_mode(TRANSMITTER);
nRF24_enable_feature(FEATURE_EN_DPL, true);
nRF24_set_TX_addr(nRF24_addr0, 5);
nRF24_set_RX_addr(0, nRF24_addr0, 5);
nRF24_enable_data_pipe_dynamic_payload_length(0, true);

  • 接收端設定

nRF24_spi_default_init(20, 21, nRF24_irq_callback);
nRF24_config_mode(RECEIVER);
nRF24_enable_feature(FEATURE_EN_DPL, true);
nRF24_set_RX_addr(0, nRF24_addr0, 5);
nRF24_enable_data_pipe_dynamic_payload_length(0, true);

  • nRF24_irq_callback:

void nRF24_irq_callback(uint8_t event_type, uint8_t datapipe, uint8_t* data, uint8_t width) {
    static uint8_t XY_AXIS;
    static uint8_t ang[4];
    uint8_t data_buffer[33];
    uint8_t data_len; 
      switch(event_type) {
        case EVENT_RX_DR:
#ifdef TRANS_SIDE
#else   
            data_len = width;
            memcpy(data_buffer, data, width);
            deencrypt_data(data_buffer, &data_len);
            XY_AXIS=data_buffer[0];
            memset(ang,0,sizeof(ang));
            memcpy(ang, data_buffer+2, width-2);
            if (XY_AXIS == 'X') {
                setServoAngle(atoi(ang), pwm_ch_x, pwm_slice_x);
            }
            if (XY_AXIS == 'Y') {
                setServoAngle(atoi(ang), pwm_ch_y, pwm_slice_y);
            }
#endif
                
        break;
        case EVENT_TX_DS:
        break;
        case EVENT_MAX_RT:
        break;
    }
}

四、成果影片:



五、程式碼:
main.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/clocks.h"
#include "stdlib.h"
#include "hardware/pwm.h"
#include "hardware/adc.h"
#include "nRF24L01.h"
#include "string.h"

#define PWM_X_PIN   14
#define PWM_Y_PIN   15
uint pwm_ch_x, pwm_slice_x;
uint pwm_ch_y, pwm_slice_y;
int32_t servo_x_ang, servo_y_ang;
#define ADC_X_PIN   26
#define ADC_X_NUM   0
#define ADC_Y_PIN   27
#define ADC_Y_NUM   1
#define ADC_RANGE (1<<12)    // pi pico 12 bits ADC
int32_t adc_x_center, adc_y_center;

#define TRANS_SIDE

uint8_t nRF24_addr0[] = "0node";

void encrypt_data(uint8_t *data, uint8_t *len) {
    // do data encryption
    return;
}

void deencrypt_data(uint8_t *data, uint8_t *len) {
    // do data deencryption
    return;
}
void setServoAngle(int ang, uint pwm_ch, uint slice) {
    //uint16_t top_count = (uint16_t)(1000/180*ang + 1500);  // angle 0: 1.5 ms duty
    uint16_t top_count = (uint16_t)(ang/0.09 + 1500);  // angle 0: 1.5 ms duty
    pwm_set_chan_level(slice, pwm_ch, top_count);
}

void servo_init(uint pin, uint *pwm_ch, uint* slice) {
    // set Servo pwm
    gpio_set_function(pin, GPIO_FUNC_PWM);
    *slice = pwm_gpio_to_slice_num(pin);
    *pwm_ch = pwm_gpio_to_channel(pin);
    pwm_config c = pwm_get_default_config();
    pwm_config_set_clkdiv(&c, 125);  // 20ms period, steps 20000, clkdiv = 125 
    pwm_config_set_wrap(&c, 20000);
    pwm_config_set_phase_correct(&c, false);
    pwm_init(*slice, &c, true);    
}

void nRF24_irq_callback(uint8_t event_type, uint8_t datapipe, uint8_t* data, uint8_t width) {
    static uint8_t XY_AXIS;
    static uint8_t ang[4];
    uint8_t data_buffer[33];
    uint8_t data_len; 
      switch(event_type) {
        case EVENT_RX_DR:
#ifdef TRANS_SIDE
#else   
            data_len = width;
            memcpy(data_buffer, data, width);
            deencrypt_data(data_buffer, &data_len);
            XY_AXIS=data_buffer[0];
            memset(ang,0,sizeof(ang));
            memcpy(ang, data_buffer+2, width-2);
            if (XY_AXIS == 'X') {
                setServoAngle(atoi(ang), pwm_ch_x, pwm_slice_x);
            }
            if (XY_AXIS == 'Y') {
                setServoAngle(atoi(ang), pwm_ch_y, pwm_slice_y);
            }
#endif
        break;
        case EVENT_TX_DS:


        break;
        case EVENT_MAX_RT:

        break;
    }
}

int main()
{
    uint8_t payload[33];
    uint8_t payload_len;
    stdio_init_all();
    nRF24_spi_default_init(20, 21, nRF24_irq_callback);

#ifdef TRANS_SIDE
    nRF24_config_mode(TRANSMITTER);
    nRF24_enable_feature(FEATURE_EN_DPL, true);
    nRF24_set_TX_addr(nRF24_addr0, 5);
    nRF24_set_RX_addr(0, nRF24_addr0, 5);
    nRF24_enable_data_pipe_dynamic_payload_length(0, true);

    adc_init();                     // init and assign ADC pin
    adc_gpio_init(ADC_X_PIN);
    adc_gpio_init(ADC_Y_PIN);
    for (int i=0; i < 100; i++) {     // average 100 samples as mouse static position
        adc_select_input(ADC_X_NUM);
        adc_x_center += adc_read();
        adc_select_input(ADC_Y_NUM);
        adc_y_center += adc_read();
    }
    adc_x_center /=100;
    adc_y_center /=100;
#else 
    nRF24_config_mode(RECEIVER);
    nRF24_enable_feature(FEATURE_EN_DPL, true);
    nRF24_set_RX_addr(0, nRF24_addr0, 5);
    nRF24_enable_data_pipe_dynamic_payload_length(0, true);

    servo_y_ang = 0;
    servo_x_ang = 0;
    servo_init(PWM_X_PIN, &pwm_ch_x, &pwm_slice_x);
    servo_init(PWM_Y_PIN, &pwm_ch_y, &pwm_slice_y);

    setServoAngle(servo_x_ang, pwm_ch_x, pwm_slice_x);
    setServoAngle(servo_y_ang, pwm_ch_y, pwm_slice_y);
#endif 
    int32_t adc_raw;
    while(1) {
#ifdef TRANS_SIDE
        adc_select_input(ADC_X_NUM);
        adc_raw = adc_read();
        if(abs(adc_raw-adc_x_center) > 60) {
            //servo_x_ang=(adc_raw-adc_x_center)*90/adc_x_center;
            if (adc_raw < adc_x_center) {
                servo_x_ang++;
                if (servo_x_ang > 90) servo_x_ang = 90;
            }
            else { 
                servo_x_ang--;
                if (servo_x_ang < -90) servo_x_ang = -90;
           }
            //setServoAngle(servo_x_ang, pwm_ch_x, pwm_slice_x);
            sprintf(payload,"X:%d", servo_x_ang);
            payload_len = strlen(payload); 
            encrypt_data(payload, &payload_len);
            nRF24_write_payload(payload, payload_len);
            printf("payload:%s\n", payload);
        }

        adc_select_input(ADC_Y_NUM);
        adc_raw = adc_read();
        if(abs(adc_raw-adc_y_center) > 60) {
            if (adc_raw < adc_y_center) {
                servo_y_ang++;
                if (servo_y_ang > 90) servo_y_ang = 90;
            }
            else { 
                servo_y_ang--;
                if (servo_y_ang < -90) servo_y_ang = -90;
            }
            //setServoAngle(servo_y_ang, pwm_ch_y, pwm_slice_y);
            sprintf(payload,"Y:%d", servo_y_ang);
            payload_len = strlen(payload); 
            encrypt_data(payload, &payload_len);
            nRF24_write_payload(payload, payload_len);
            printf("payload:%s\n", payload);
        }
        //printf("payload:%s\n", payload);
#endif
       sleep_ms(20);
       
    }

    return 0;
}

2023年8月22日 星期二

PWM Ep. 1 : Square Wave Frequency and Duty Cycle Measurement -- Raspberry Pi Pico and STM32F103C8T6

本文章探討利用Raspberry Pi Pico來量測輸入PWM方波的頻率與佔空比。以STM32F103C8T6來產生PWM方波讓Pico來量測。

一、RP2040 PWM:

(source: RP2040 datasheet)
RP2040 共有8 PWM slices,每個slice有channel A與B,與GPIO對應表如自如所示。每個slice channel A and B均能輸出PWM方波。但只有channel B能作為輸入PWM用。

RP2040輸出方波的波型主要由下列三個主要參數決定。
  1. 8.4除瀕(8 bits整數,4 bits小數) :
    0: max(256)。整數:1~255,小數:0~(15/16)。
    pwm_set_clkdiv(slice_num, div);
  2. 16 bit 計數值(TOP register):
    pwm_set_wrap(slice_num, top_wap);
    top_wap: 0~65535
  3. 16 bit 計數比較值(CC register):
    pwm_set_chan_level(slice_num, PWM_CHAN_A, cc);
    cc: 0~65536
例如:頻率為500k佔空比為40%的方波
系統clock為125M Hz,
pwm_set_clkdiv(slice_num 12.5) --> 125M/12.5=10M
pwm_set_warp(slice_num, 19) --> 10M/(19+1) = 500k
pwm_set_chan_level(slice_num, PWM_CHAN_A, 8) --> 8/(19+1) = 40%

(Source RP2040 datasheet)

  • PWM輸入:
僅有每個slice的Channel B能接受輸入。
(source RP2040 datasheet)

將Channel B設為輸入有三種模式,分別為偵測High Leve, rising edge與falling edge。
pwm_config_set_clkdiv_mode(&cfg, mode);
pwm_set_clkdiv_mode(slice_num, mode);
mode:PWM_DIV_B_HIGH, PWM_DIV_B_RISING, PWM_DIV_B_FALLING

  1. 設定PWM_DIV_B_HIGH時可用來偵測Duty Cycle:
    當channel偵測到input PWM pulse level為high時,CC count 加 1,register為16 bits,因此最答值為65535。例如:
    slice_num : 3, click div: 100, 偵測時間為50ms。則channel B最大clock數為:
    125M Hz/100*(50/1000) =  62500 Hz。
    pwm_set_clkdiv_mode(slice_num, PWM_DIV_B_HIGH);    pwm_set_clkdiv(slice_num, 100);
    pwm_set_enabled(slice_num, true);
    sleep_ms(50);
    pwm_set_enabled(slice_num, false);

    上述為channel B 50ms總clock數。
    count=pwm_get_counter(slice_num);
    當channel B偵測到input PWM pulse為high時, count+1,因此
    count / 625000 * 100%即為Duty cycle。
  2. 設定PWM_DIV_B_RISING or PWM_DIV_B_FALLING時可用來偵測Frequency:
    當channel B偵測到input PWM pulse為RISING or FALLING時count+1。pwm_config cfg = pwm_get_default_config();    pwm_config_set_clkdiv_mode(&cfg, PWM_DIV_B_RISING);    pwm_config_set_clkdiv(&cfg, 1);   
    pwm_init(slice_num, &cfg, false);    gpio_set_function(gpio, GPIO_FUNC_PWM);
    以125M Hz偵測。
    pwm_set_enabled(slice_num, true);       
    sleep_ms(delay_ms);       
    pwm_set_enabled(slice_num, false);       
    rising_count = pwm_get_counter(slice_num);

    經過delay_ms後,偵測到
    rising_count個RISING EDGE,因此
    Freq = rising_count / delay_ms*1000 Hz
    但是rising_count最多為65535(16 bit register),因此必須根據input pulse frequence調整delay_ms數。
其他詳細程式碼附於文末。

二、STM32 PWM:

STM32 PWM由timer產生。

如上圖timer 1 clock為72 M Hz。
clock source設為internal clock(72M Hz),prescaler, counter period(ARR)分別為1, 299,因此PWM的Frequency為72M Hz/(1+1)/(299+1) = 120K Hz

Duty cycle=150/(299+1)*100% = 50% 

htim1.Instance->ARR = fs_presc;
htim1.Instance->CCR1 = fs_presc/duty;
改變ARR與CCR可以改變frequency 與duty cycle

三、展示影片:



四、程式碼:

 
#include <stdio.h>

#include "pico/stdlib.h"
#include "hardware/pwm.h"
#include "hardware/clocks.h"
#include "string.h"
#include "pico_tft/pico_tft.h"
#include "pico_tft/tft_string/tft_string.h"
#include "pico_tft/fonts/font_ubuntu_mono_24.h"

const uint MEASURE_PIN_1 = 3;
const uint MEASURE_PIN_2 = 5;
bool pwm_slice_warp_1=false;
bool pwm_slice_warp_2=false;

void on_pwm_wrap() {

   uint slice_num_1 = pwm_gpio_to_slice_num(MEASURE_PIN_1);
    if (pwm_get_irq_status_mask() & (1 << slice_num_1)) {
        pwm_clear_irq(slice_num_1);
        pwm_slice_warp_1=true;
        
    }

    uint slice_num_2 = pwm_gpio_to_slice_num(MEASURE_PIN_2);
    if (pwm_get_irq_status_mask() & (1 << slice_num_2)) {
        pwm_clear_irq(slice_num_2);
        pwm_slice_warp_2=true;
        
    }
}


float measure_frequency(uint gpio) {
    uint16_t measure_ms[] = {1,5,10,100,1000};
    bool *pwm_slice_warp=false;

    // Only the PWM B pins can be used as inputs.
    assert(pwm_gpio_to_channel(gpio) == PWM_CHAN_B);
    uint slice_num = pwm_gpio_to_slice_num(gpio);

    if (gpio == MEASURE_PIN_1) pwm_slice_warp = &pwm_slice_warp_1;
    if (gpio == MEASURE_PIN_2) pwm_slice_warp = &pwm_slice_warp_2;

    // Count once for every 1 cycles the PWM B input is high
    pwm_config cfg = pwm_get_default_config();
    pwm_config_set_clkdiv_mode(&cfg, PWM_DIV_B_RISING);
    pwm_config_set_clkdiv(&cfg, 1);
    pwm_init(slice_num, &cfg, false);
    gpio_set_function(gpio, GPIO_FUNC_PWM);
    
    pwm_set_irq_enabled(slice_num, true);
    irq_set_exclusive_handler(PWM_IRQ_WRAP, on_pwm_wrap);
    irq_set_enabled(PWM_IRQ_WRAP, true);

    uint32_t ms=0;
    uint16_t count=0;
    uint8_t meauser_count = sizeof(measure_ms)/sizeof(measure_ms[0]);
    *pwm_slice_warp=false;
    for (int i=0; i < meauser_count;i++) {
        ms = measure_ms[i];
        pwm_set_enabled(slice_num, true);
        sleep_ms(ms);
        pwm_set_enabled(slice_num, false);
        count = pwm_get_counter(slice_num);
        pwm_set_counter(slice_num,0);

        if (*pwm_slice_warp) {
            *pwm_slice_warp=false;
            if (i > 0) {
                 ms = measure_ms[i-1];
                pwm_set_enabled(slice_num, true);
                sleep_ms(ms);
                pwm_set_enabled(slice_num, false);
                count = pwm_get_counter(slice_num);
                pwm_set_counter(slice_num,0);
            }
            break;
            
        } 
 
    }

    return ((float)count*1000/(ms));
    
}

float measure_duty_cycle(uint gpio) {
    // Only the PWM B pins can be used as inputs.
    assert(pwm_gpio_to_channel(gpio) == PWM_CHAN_B);
    uint slice_num = pwm_gpio_to_slice_num(gpio);

    // Count once for every 100 cycles the PWM B input is high
    pwm_config cfg = pwm_get_default_config();
    pwm_config_set_clkdiv_mode(&cfg, PWM_DIV_B_HIGH);
    pwm_config_set_clkdiv(&cfg, 100);
    pwm_init(slice_num, &cfg, false);
    gpio_set_function(gpio, GPIO_FUNC_PWM);
    uint16_t count=0;
    pwm_set_counter(slice_num,0);

    pwm_set_enabled(slice_num, true);
    sleep_ms(50);
    pwm_set_enabled(slice_num, false);
    count=pwm_get_counter(slice_num);

    float counting_rate = clock_get_hz(clk_sys) / 100;
    float max_possible_count = counting_rate *0.05;   // 125M/100*0.05 = 62500 < 65535

    return pwm_get_counter(slice_num)/max_possible_count;
}

void test_500k_50() {
    gpio_set_function(6, GPIO_FUNC_PWM);
    uint slice_num = pwm_gpio_to_slice_num(6);
    pwm_config cfg = pwm_get_default_config();
    pwm_config_set_clkdiv(&cfg, 12.5);
    pwm_config_set_wrap(&cfg, 19);
    pwm_init(slice_num, &cfg, true);

    pwm_set_chan_level(slice_num, PWM_CHAN_A, 10);
    
}

int main() {
    stdio_init_all();
    tft_init();
    tft_fill_rect(0,0, TFT_WIDTH-1, TFT_HEIGHT-1, 0xffff);
    printf("\nPWM frequency and duty cycle measurement example\n");
    uint8_t buffer[120];
    float duty, frequency;
    while (1) {
        //sleep_ms(1000);
        frequency = measure_frequency(MEASURE_PIN_1);
        duty = measure_duty_cycle(MEASURE_PIN_1)*100;
        sprintf(buffer, "F1:%.0f", frequency);
        tft_draw_string_withbg(10,10, "            ", 0x001f, 0xffff, &font_ubuntu_mono_24);
        tft_draw_string_withbg(10,10, buffer, 0x001f, 0xffff, &font_ubuntu_mono_24);
        sprintf(buffer, "D1:%02.2f%%", duty);
        tft_draw_string_withbg(10,35, buffer, 0xf800, 0xffff, &font_ubuntu_mono_24);

        printf("\nMEASURE 1 == Freq:%07.0f, Duty:%02.2f%%\n", frequency, duty);
       
        frequency = measure_frequency(MEASURE_PIN_2);
        duty = measure_duty_cycle(MEASURE_PIN_2)*100;
        sprintf(buffer, "F2:%.0f", frequency);
        tft_draw_string_withbg(10,65, "            ", 0x001f, 0xffff, &font_ubuntu_mono_24);
        tft_draw_string_withbg(10,65, buffer, 0x001f, 0xffff, &font_ubuntu_mono_24);
        sprintf(buffer, "D2:%02.2f%%", duty);
        tft_draw_string_withbg(10,90, buffer, 0xf800, 0xffff, &font_ubuntu_mono_24);
       
        printf("\nMEASURE 2 == Freq:%07.0f, Duty:%02.2f%%\n", frequency, duty);
        
    }
}

2023年8月16日 星期三

[Raspberry Pi Pico W] BTstack: Ep 6. HID device, Mouse and ADC Joystick

 這篇文章介紹Raspberry Pi Pico W使用BTstack bluetooth HID功能,製作一組HID Bluetooth Mouse。使用模擬設備為HW-504 joystick 模組。

HW-504為一個x-y方向2D的電位計,因此在Pi Pico W使用GPIO pin 26(ADC NUM 0)接VRx腳位,GPIO pin 27(ADC NUM 1)接VRy腳位,SW開關壓下時為接地,本實驗使用的模組未接上拉電位,因此要先接一個上拉電阻。

BTstack example 有一個hid_mouse_demo.c程式碼,本次實驗直接使用,因為在檔案中很多宣告為static,所以在主程式中#include "hid_mouse_demo.c"當作程式的一部分。

根據 HID Descriptor, input report含有三個bytes,第一個byte 最小三個位元分別為代表滑鼠按鈕,值為0與1,第2個byte為x軸移動速度,值為-127~127,第個byte為y軸移動速度,值為-127~127。


Pico W的ADC 為12 bits,因此取樣值為0~4095,而HW-504模組輸出需對應到-127~127之間。因為HW-504模組輸出靈敏度較差,因此本實驗將x軸與y軸輸出定在-8~8之間。
主程式如下說明:
理論上ADC取樣值0~2047應對應到dx or dy:-8~0。將ADC取樣值設每256為一個階級。

起始時當HW-504模組靜止x與y軸個取樣100次平均當作中間的參考值。

取樣結束判斷是否有按下按鍵或是移動座標。送出send report要求事件。
hid_device_request_can_send_now(hid_cid);

展示影片:


程式碼如下:
hid_mouse_demo.c需作下列三項修正。


btstack_config.h檔案內容可參閱前篇文章內容。

  • picow_bt_mouse_joystick.c
 #include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/adc.h"
#include "hardware/gpio.h"
#include "stdlib.h"
#include "pico/cyw43_arch.h"
#include "hid_mouse_demo.c"

#define ADC_X_PIN   26
#define ADC_X_NUM   0
#define ADC_Y_PIN   27
#define ADC_Y_NUM   1
#define ADC_VREF    3.3
#define ADC_RANGE (1<<12)    // pi pico 12 bits ADC


#define BUTTON_PIN  16
#define STEP        256   // min 16, 2048/256=8, mouse moving speed 0~8

uint32_t adc_x_center=0;
uint32_t adc_y_center=0;

int main()
{
    stdio_init_all();
    if (cyw43_arch_init()) {
        printf("cyw43 arch init error\n");
        return 0;
    }

    btstack_main(0, NULL);
    
    gpio_init(BUTTON_PIN);
    gpio_set_dir(BUTTON_PIN, false);
    adc_init();                     // init and assign ADC pin
    adc_gpio_init(ADC_X_PIN);
    adc_gpio_init(ADC_Y_PIN);

    for (int i=0; i < 100; i++) {     // average 100 samples as mouse static position
        adc_select_input(ADC_X_NUM);
        adc_x_center += adc_read();
        adc_select_input(ADC_Y_NUM);
        adc_y_center += adc_read();
    }
    adc_x_center /=100;
    adc_y_center /=100;
    printf("x:%d, y:%d\n", adc_x_center, adc_y_center);
    

    uint adc_raw=ADC_RANGE/2;
    uint adc_x=ADC_RANGE/2;
    uint adc_y=ADC_RANGE/2;
    bool button_pressed=false;
    bool mouse_moving=false;
    while(1) {
        
        if (abs(adc_x-adc_x_center) < 10 &&  abs(adc_y-adc_y_center) < 10)
        {
            mouse_moving=false;
            dx=dy=0;
        }
        
        if (!gpio_get(BUTTON_PIN)) {
            if(!button_pressed) {
                button_pressed = true;
                printf("button pressed\n");
                buttons |= 1;
                dx=dy=0;
                mouse_moving = false;
            } 
        } else {
            button_pressed = false;
            buttons = 0;
            adc_select_input(ADC_X_NUM);
            adc_raw = adc_read();
            if(abs(adc_raw-adc_x_center) > 10) {   
                adc_x=adc_raw;
                dx = (adc_raw - ADC_RANGE/2)/STEP;
                mouse_moving=true;
            }
                
            adc_select_input(ADC_Y_NUM);
            adc_raw = adc_read();
            if(abs(adc_raw-adc_y_center) > 10) {
                adc_y=adc_raw;
                dy = (adc_raw - ADC_RANGE/2)/STEP;
                mouse_moving=true;
            }
        }
        if (dx || dy || buttons) {
            hid_device_request_can_send_now_event(hid_cid);
        }
        sleep_ms(10);
    }

    return 0;
}

  • CMakeLists.txt
 # Generated Cmake Pico project file

cmake_minimum_required(VERSION 3.13)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
set(PICO_SDK_PATH "/home/duser/pico/pico-sdk")

set(PICO_BOARD pico_w CACHE STRING "Board type")

# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)

if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0")
  message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}")
endif()

project(picow_bt_mouse_joystick C CXX ASM)

# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()

# Add executable. Default name is the project name, version 0.1

add_executable(picow_bt_mouse_joystick picow_bt_mouse_joystick.c )

pico_set_program_name(picow_bt_mouse_joystick "picow_bt_mouse_joystick")
pico_set_program_version(picow_bt_mouse_joystick "0.1")

pico_enable_stdio_uart(picow_bt_mouse_joystick 1)
pico_enable_stdio_usb(picow_bt_mouse_joystick 0)

# Add the standard library to the build
target_link_libraries(picow_bt_mouse_joystick
        pico_stdlib
        hardware_adc
        pico_util
        pico_cyw43_arch_none
        pico_btstack_cyw43
        pico_btstack_classic)

# Add the standard include files to the build
target_include_directories(picow_bt_mouse_joystick PRIVATE
  ${CMAKE_CURRENT_LIST_DIR}
  ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required
)

pico_add_extra_outputs(picow_bt_mouse_joystick)

2023年8月13日 星期日

STM32 HAL nRF24L01 c code driver using IRQ

本篇文章介紹在STM32CubeIDE環境下使用HAL函式庫製作nRF24L01+驅動程式,使用IRQ pin,以GPIO interrupt處理nRF24L01的Data Sent, Data ready與max re-transmission interrupt。

一、使用方法:

  1. 使用STM32CubeIDE 的STM32CubeMX工具設定SPI pin。

    以STM32F013C8T6為例

    PA2->nRF24L01 IRQ, 
    PA3 -> CE,
    PA4->CSn,
    PA5->SCK,
    PA6->MISO,
    PA7->MOSI。


    PA2 IRQ pin指定falling edge並enable NVIC。
  2. 將nRF24L01.c複製到src資料夾,nRF24L01.h複製到inc資料夾下。
  3. 覆寫__weak function (IRQ callback)
    __weak void nRF24_irq_callback(uint8_t event_type, uint16_t data_src, uint8_t* data, uint8_t width)


    參數:
    event_type:
    EVENT_RX_DR為nRF24L01接收資料時處理程序。
    EVENT_TX_RS為送完資料成功收到ACK時處理程序。
    EVENT_MAX_RT為達到重送最大值時處理程序。
    EVENT_GPIO_IRQ為非nRF24L01的其他GPIO IRQ處理程序。 
    data_src: 接收資料時的datapipe,不是nRF24L01的interrupt時,為其他GPIO interrupt 的GPIO pin number。
    data:收取資料的內容。
    width:data的長度。

  4. Driver initialize:
    nRF24_spi_init(&hspi1, MX_nRF24_CSn_GPIO_Port, MX_nRF24_CSn_Pin, MX_nRF24_CE_GPIO_Port, MX_nRF24_CE_Pin,
            MX_nRF24_IRQ_GPIO_Port, MX_nRF24_IRQ_Pin);
  5. 指定為Transmitter or Receiver:
    nRF24_config_mode(role); 1:Receiver, 0:transmitter。
  6. enable Feature:
    nRF24_enable_feature(FEATURE_EN_DPL, 1);
  7. 指定位址:
    nRF24_set_RX_addr(0, address, 5);
    nRF24_set_TX_addr(address, 5);

  8. 設定datapipe 啟用dynamic payload length:
    nRF24_enable_data_pipe_dynamic_payload_length(0, 1);// 1: enable, 0:disable 
  9. Transmitter傳送payload:
    nRF24_write_payload(payload, lenght);

二、範例

下列範例為Receiver收到payload後,轉換成Transmitter傳送payload至其他node:
展示影片:


  • main.c
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "nRF24L01.h"
#include "string.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
SPI_HandleTypeDef hspi1;

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_SPI1_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
//#define nRF24_RECEIVER
uint8_t can_send=0;
uint8_t send_buffer[33];

uint8_t addr[6][5] = {"0node", "1node", "2node","3node","4node","5node"};
uint8_t transnode=1;
uint8_t total_nodes=4;
uint8_t send_buffer[33];
uint8_t role, new_role;

void nRF24_irq_callback(uint8_t event_type, uint16_t data_src, uint8_t* data, uint8_t width) {

      switch(event_type) {
        case EVENT_RX_DR:
        	HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
			can_send=1;
			new_role = TRANSMITTER;
        break;
        case EVENT_TX_DS:
        	HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
        	new_role=RECEIVER;
        	can_send=0;
        break;
        case EVENT_MAX_RT:

        break;
        case EVENT_GPIO_IRQ:

        	break;
    }
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_SPI1_Init();
  /* USER CODE BEGIN 2 */
  role = RECEIVER;
  new_role=role;
   nRF24_spi_init(&hspi1, MX_nRF24_CSn_GPIO_Port, MX_nRF24_CSn_Pin, MX_nRF24_CE_GPIO_Port, MX_nRF24_CE_Pin,
    		  	  MX_nRF24_IRQ_GPIO_Port, MX_nRF24_IRQ_Pin);
    nRF24_enable_feature(FEATURE_EN_DPL, 1);
  	nRF24_config_mode(role);
    nRF24_enable_RXADDR(0b00000011);

    nRF24_set_RX_addr(0, addr[(transnode+1)%total_nodes], 5);
    nRF24_set_TX_addr(addr[(transnode+1)%total_nodes], 5);
    nRF24_set_RX_addr(1, addr[transnode], 5);

    nRF24_enable_data_pipe_dynamic_payload_length(0, 1);
    nRF24_enable_data_pipe_dynamic_payload_length(1, 1);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
    int count=0;
  while (1)
  {
	  if (role != new_role) {
		  role = new_role;
		  nRF24_config_mode(role);
	  }
	  if (can_send && role==TRANSMITTER) {
		  can_send=0;
		  sprintf((char*)send_buffer, "0:abcdefgh%d", count++);
		  count %=10000;
		  nRF24_write_payload(send_buffer,strlen((char*)send_buffer));
	  }
	  HAL_Delay(1000);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief SPI1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_SPI1_Init(void)
{

  /* USER CODE BEGIN SPI1_Init 0 */

  /* USER CODE END SPI1_Init 0 */

  /* USER CODE BEGIN SPI1_Init 1 */

  /* USER CODE END SPI1_Init 1 */
  /* SPI1 parameter configuration*/
  hspi1.Instance = SPI1;
  hspi1.Init.Mode = SPI_MODE_MASTER;
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi1.Init.NSS = SPI_NSS_SOFT;
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi1.Init.CRCPolynomial = 10;
  if (HAL_SPI_Init(&hspi1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SPI1_Init 2 */

  /* USER CODE END SPI1_Init 2 */

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOA, MX_nRF24_CE_Pin|MX_nRF24_CSn_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin : LED_Pin */
  GPIO_InitStruct.Pin = LED_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(LED_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pin : MX_nRF24_IRQ_Pin */
  GPIO_InitStruct.Pin = MX_nRF24_IRQ_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(MX_nRF24_IRQ_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pins : MX_nRF24_CE_Pin MX_nRF24_CSn_Pin */
  GPIO_InitStruct.Pin = MX_nRF24_CE_Pin|MX_nRF24_CSn_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /* EXTI interrupt init*/
  HAL_NVIC_SetPriority(EXTI2_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(EXTI2_IRQn);

/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

三、驅動程式碼:

  • nRF24L01.c
 #include <stdio.h>
#include "string.h"
#include "main.h"
#include "nRF24L01.h"

// SPI Defines
static SPI_HandleTypeDef* nRF24_SPI;
static GPIO_TypeDef* nRF24_GPIO_CE_port=GPIOB;
static GPIO_TypeDef* nRF24_GPIO_CSn_port=GPIOB;
static GPIO_TypeDef* nRF24_GPIO_IRQ_port=GPIOB;
static uint16_t nRF24_GPIO_CE_pin=GPIO_PIN_6;
static uint16_t nRF24_GPIO_CSn_pin=GPIO_PIN_7;
static uint16_t nRF24_GPIO_IRQ_pin=GPIO_PIN_8;

static uint32_t nRF24_SPI_TIMEOUT=1000;

//nRF24_irq_callback_t nRF24_callback;
static uint8_t nRF24_data_buffer[64];

static void nRF24_standby_to_txrx_mode();
static void nRF24_standby_I();
static void nRF24_reset_REGISTER();
static void nRF24_spi_enable();
static void nRF24_spi_disable();

/*!
* \brief STM32 GPIO IRQ Callback
* 		 nRF24L01 IRQ callback function
* \param gpio: gpio number
*/
void HAL_GPIO_EXTI_Callback(uint16_t gpio) {
	uint8_t status;
	uint8_t event_type;
	uint16_t data_src;
	uint8_t width=0;
	memset(nRF24_data_buffer,0, 32);
	if (gpio == nRF24_GPIO_IRQ_pin) {
		nRF24_status(&status);
		data_src = (status & 0x0E) >> 1;
		if ((status & 0x40) >> EVENT_RX_DR) {  // RX_DR
			event_type = EVENT_RX_DR;

		   if (nRF24_get_RX_payload_width(&width) == HAL_OK) {
				if (width > 32) { // in nRF24L01+ product specification
					printf("...in driver width > 32 datapipe:%d:%d\n", data_src, width);
					nRF24_standby_I();

					nRF24_flush_rx();       //

					nRF24_standby_to_txrx_mode();
				}
				else {
					nRF24_read_payload(nRF24_data_buffer, width);
					nRF24_irq_callback(event_type, data_src, nRF24_data_buffer, width);
				}
				nRF24_clear_RX_DR();
			}
		}

		if ((status & 0x20) >> EVENT_TX_DS) {  // TX_DS
			event_type = EVENT_TX_DS;
			nRF24_irq_callback(event_type, data_src, nRF24_data_buffer, 0);
			nRF24_clear_TX_DS();
		}

		if ((status & 0x10) >> EVENT_MAX_RT) {  // MAX_RT
			event_type = EVENT_MAX_RT;
			nRF24_irq_callback(event_type, data_src, nRF24_data_buffer, 0);
			nRF24_clear_MAX_RT();
		}

	} else {
		event_type = EVENT_GPIO_IRQ;  // STM32 gpio irq except nRF24L01 IRQ
		nRF24_irq_callback(event_type, gpio, nRF24_data_buffer, width);
	}
}
/*!
* \brief set nRF24L01 PWR_UP in CONFIG register
*/
static void nRF24_config_PWR_UP() {
    uint8_t ret;
    uint8_t mode;
    
    ret = nRF24_read_REGISTER(CONFIG, &mode,1);
    if (ret == HAL_OK) {
        mode  |= (0x02);
        ret = nRF24_write_REGISTER(CONFIG, &mode,1);
    }
}

/*!
* \brief enable CE, set CE high
*/
static void nRF24_enable_CE() {
    //gpio_put(nRF24_CE, enable);
		HAL_GPIO_WritePin(nRF24_GPIO_CE_port, nRF24_GPIO_CE_pin, GPIO_PIN_SET);
}

/*!
* \brief enable CE, set CE low
*/
static void nRF24_disable_CE() {
    //gpio_put(nRF24_CE, enable);
	HAL_GPIO_WritePin(nRF24_GPIO_CE_port, nRF24_GPIO_CE_pin, GPIO_PIN_RESET);
}

static void nRF24_standby_I() {
    nRF24_disable_CE();
}

/*!
* \brief nRF24L01 active FEATURE. nRF24L01+ does not need this function
*/
uint8_t nRF24_activate() {
    uint8_t ret;
    uint8_t status;
    uint8_t value = 0x73;
    uint8_t cmd = ACTIVATE;

    nRF24_spi_enable();
    //ret = spi_write_blocking(SPI_PORT, cmd, 2);
    ret = HAL_SPI_TransmitReceive(nRF24_SPI, &cmd, &status, 1, nRF24_SPI_TIMEOUT);
    if (ret == HAL_OK) {
    	ret = HAL_SPI_Transmit(nRF24_SPI, &value, 1, nRF24_SPI_TIMEOUT);
    }
    nRF24_spi_disable();

    return ret;
}

/*!
* \brief 1. set SPI pin and initialize.  
*         2.set nRF24L01 to standby-I mode
* \param spi SPI handle
* \param Csn_port GPIO port
* \param Csn_pin GPIO pin
* \param ce_port nRF24L01 CE GPIO port
* \param ce_pin nRF240L01 CE GPIO pin
* \param irq_port nRF24L01 IRQ GPIO port
* \param irq_pin nRF24L01 IRQ GPIO pin
*/
void nRF24_spi_init(SPI_HandleTypeDef* spi, GPIO_TypeDef* csn_port, uint32_t csn_pin, GPIO_TypeDef* ce_port, uint32_t ce_pin,  GPIO_TypeDef* irq_port, uint32_t irq_pin) {

	nRF24_SPI = spi;

    nRF24_GPIO_CSn_port = csn_port;
    nRF24_GPIO_CSn_pin = csn_pin;

    nRF24_GPIO_CE_port = ce_port;
    nRF24_GPIO_CE_pin = ce_pin;

    nRF24_GPIO_IRQ_port = irq_port;
    nRF24_GPIO_IRQ_pin = irq_pin;

    nRF24_reset_REGISTER();

    nRF24_config_PWR_UP();

    //nRF24_activate();   //nRF24L01 Product Specification

    nRF24_disable_CE();  //standby-I
    HAL_Delay(1);
}

/*!
* \brief enable SPI, set CS LOW.
*/
void nRF24_spi_enable() {

		HAL_GPIO_WritePin(nRF24_GPIO_CSn_port, nRF24_GPIO_CSn_pin, GPIO_PIN_RESET);
}

/*!
* \brief disable SPI, set CS HIGH.
*/
void nRF24_spi_disable() {

		HAL_GPIO_WritePin(nRF24_GPIO_CSn_port, nRF24_GPIO_CSn_pin, GPIO_PIN_SET);
}

/*!
* \brief read nRF24L01 register
* \param REGISTER register address
* \param value read out data
* \param len read out bytes
* \return HAL_status
*/
uint8_t  nRF24_read_REGISTER(uint8_t REGISTER, uint8_t *value, uint8_t len) {
    uint8_t ret;
    uint8_t status;
    uint8_t cmd=R_REGISTER|REGISTER;
    
    nRF24_spi_enable();
    ret = HAL_SPI_TransmitReceive(nRF24_SPI, &cmd, &status,1 , nRF24_SPI_TIMEOUT);
    if (ret == HAL_OK) {
    	ret = HAL_SPI_Receive(nRF24_SPI, value, len, nRF24_SPI_TIMEOUT);
    }
    nRF24_spi_disable();
    return ret;
}

/*!
* \brief write data into  nRF24L01 register
* \param REGISTER register address
* \param value write data
* \param len write bytes
* \return HAL status
*/
uint8_t nRF24_write_REGISTER(uint8_t REGISTER, uint8_t *value, uint8_t len) {
    uint8_t ret;
    uint8_t status;
    uint8_t cmd =W_REGISTER | REGISTER;
    nRF24_spi_enable();
    ret = HAL_SPI_TransmitReceive(nRF24_SPI, &cmd, &status, 1, nRF24_SPI_TIMEOUT);
    if (ret == HAL_OK) {
    	ret = HAL_SPI_Transmit(nRF24_SPI, value, len, nRF24_SPI_TIMEOUT);
    }
    nRF24_spi_disable(0);

    return ret;
}

/*!
* \brief R_RX_PL_WIN: nRF24L01 received payload width
* \param width payload width
*/
uint8_t nRF24_get_RX_payload_width(uint8_t *width) {
    uint8_t ret;
    uint8_t status;
    uint8_t cmd=R_RX_PL_WID;
    
    nRF24_spi_enable();
    ret = HAL_SPI_TransmitReceive(nRF24_SPI, &cmd, &status, 1, nRF24_SPI_TIMEOUT);
    if (ret == HAL_OK) {
    	ret = HAL_SPI_Receive(nRF24_SPI, width, 1, nRF24_SPI_TIMEOUT);
    }
    nRF24_spi_disable();
    return ret;
}

/*!
* \brief R_RX_PAYLOAD: received payload
* \param payload received payload
* \param len received number of bytes 
* \return HAL status
*/
uint8_t nRF24_read_payload(uint8_t *payload, uint8_t len) {
    uint8_t ret;
    uint8_t status;
    uint8_t cmd=R_RX_PAYLOAD;
    
    nRF24_spi_enable();
    ret = HAL_SPI_TransmitReceive(nRF24_SPI, &cmd, &status, 1, nRF24_SPI_TIMEOUT);
    if (ret == HAL_OK) {
    	ret = HAL_SPI_Receive(nRF24_SPI, payload, len, nRF24_SPI_TIMEOUT);
    }
    nRF24_spi_disable();
    return ret;
}

/*!
* \brief W_TX_PAYLOAD: write payload
* \param payload received payload
* \param len received number of bytes 
* \return HAL status
*/
uint8_t nRF24_write_payload(uint8_t *payload, uint8_t len) {
    uint8_t ret;
    uint8_t status;
    uint8_t cmd = W_TX_PAYLOAD;
   
    nRF24_spi_enable();
    ret = HAL_SPI_TransmitReceive(nRF24_SPI, &cmd, &status, 1, nRF24_SPI_TIMEOUT);
    if (ret == HAL_OK) {
    	ret = HAL_SPI_Transmit(nRF24_SPI, payload, len, nRF24_SPI_TIMEOUT);
    }
    nRF24_spi_disable();
    return ret;
}

/*!
* \brief W_TX_PAYLOAD_NOACK: write payload without acknowledgment. enable EN_DYN_ACK in FEATURE 
* \param payload received payload
* \param len received number of bytes 
* \return HAL staus
*/
uint8_t nRF24_write_payload_no_ack(uint8_t *payload, uint8_t len) {
    uint8_t ret;
    uint8_t status;
    uint8_t cmd =W_TX_PAYLOAD_NOACK;
    
    nRF24_spi_enable();
    ret = HAL_SPI_TransmitReceive(nRF24_SPI, &cmd, &status, 1, nRF24_SPI_TIMEOUT);
    if (ret == HAL_OK) {
    	ret = HAL_SPI_Transmit(nRF24_SPI, payload, len, nRF24_SPI_TIMEOUT);
    }
    nRF24_spi_disable();
    return ret;
}

/*!
* \brief FLUSH RX FIFO
*/
uint8_t nRF24_flush_rx() {
    uint8_t ret;
    uint8_t cmd=FLUSH_RX;
    uint8_t status;
    nRF24_spi_enable();
    ret = HAL_SPI_TransmitReceive(nRF24_SPI, &cmd, &status, 1, nRF24_SPI_TIMEOUT);
    nRF24_spi_disable();
    return ret;
}

/*!
* \brief FLUSH TX FIFO
*/
uint8_t nRF24_flush_tx() {
    uint8_t ret;
    uint8_t cmd=FLUSH_TX;
    uint8_t status;
    nRF24_spi_enable();
    ret = HAL_SPI_TransmitReceive(nRF24_SPI, &cmd, &status, 1, nRF24_SPI_TIMEOUT);
    nRF24_spi_disable();
    return ret;
}

uint8_t nRF24_reuse_tx_pl() {
    uint8_t ret;
    uint8_t cmd=REUSE_TX_PL;
    uint8_t status;
    nRF24_spi_enable();
    ret = HAL_SPI_TransmitReceive(nRF24_SPI, &cmd, &status, 1, nRF24_SPI_TIMEOUT);
    nRF24_spi_disable();
    return ret;
}

/*!
* \brief W_ACK_PAYLOAD
*/
uint8_t nRF24_write_ack_payload(uint8_t data_pipe, uint8_t *payload, uint8_t len) {
    uint8_t ret;
    uint8_t status;
    uint8_t cmd=W_ACK_PAYLOAD | (data_pipe&0x07);

    nRF24_spi_enable();
    ret = HAL_SPI_TransmitReceive(nRF24_SPI, &cmd, &status, 1, nRF24_SPI_TIMEOUT);
    if (ret == HAL_OK) {
    	ret = HAL_SPI_Transmit(nRF24_SPI, payload, len, nRF24_SPI_TIMEOUT);
    }
    nRF24_spi_disable();
    return ret;
}

uint8_t nRF24_set_TX_addr(uint8_t *addr, uint8_t len) {
    
    return (nRF24_write_REGISTER(TX_ADDR, addr, len));
}

uint8_t nRF24_get_TX_addr(uint8_t *addr, uint8_t len) {
    return (nRF24_read_REGISTER(TX_ADDR, addr, len));
}

uint8_t nRF24_set_RX_addr(uint8_t data_pipe, uint8_t *addr, uint8_t len) {
    uint8_t rx_addr_reg = RX_ADDR_P0 + data_pipe;
    if (data_pipe > 1) len = 1;
    return (nRF24_write_REGISTER(rx_addr_reg, addr, len));
}

uint8_t nRF24_get_RX_addr(uint8_t data_pipe, uint8_t *addr, uint8_t len) {
    uint8_t rx_addr_reg = RX_ADDR_P0 + data_pipe;
    if (data_pipe > 1) len = 1;
    return (nRF24_read_REGISTER(rx_addr_reg, addr, len));
}

uint8_t nRF24_status(uint8_t *status) {
    uint8_t ret;
    uint8_t cmd = NOP;
    nRF24_spi_enable();
    ret = HAL_SPI_TransmitReceive(nRF24_SPI, &cmd, status, 1, nRF24_SPI_TIMEOUT);
    nRF24_spi_disable();
    return ret;
}
/*!
* \param channel 0~125
*/
uint8_t nRF24_set_RF_channel(uint8_t channel) {
    if (channel > 125) channel = 125;
    return (nRF24_write_REGISTER(RF_CH, &channel,1));
}

/*!
* \param pa: PA_0dBM, PA_m_6dBm(-6dBm), PA_m_12dBm, PA_m_18dBm
*/
uint8_t nRF24_set_power_amplifier(uint8_t pa) {
    uint8_t rf_setup;
    if(nRF24_read_REGISTER(RF_SETUP, &rf_setup,1) != HAL_OK) return HAL_ERROR;
    rf_setup &= 0xF9;
    switch(pa) {
        case PA_0dBm:
            rf_setup |= 0x06;
        break;
        case PA_m_6dBm:
            rf_setup |= 0x04;
        break;
        case PA_m_12dBm:
            rf_setup |= 0x02;
        break;
        case PA_m_18dBm:
            rf_setup |= 0x00;
        break;
    }
    return (nRF24_write_REGISTER(RF_SETUP, &rf_setup,1));
}

/*!
* \param aw 3~5
*/
uint8_t nRF24_set_address_width(uint8_t aw) {
    if (aw >=3 && aw <= 5 ) {
        aw -= 2;
        return (nRF24_write_REGISTER(SETUP_AW, &aw,1));
    }
    else 
        return HAL_ERROR;
}

uint8_t nRF24_get_address_width(uint8_t *aw) {
    if (nRF24_read_REGISTER(SETUP_AW, aw,1) != HAL_OK) {
        *aw=0;
        return HAL_ERROR;
    }
    *aw = *aw+2;
    return HAL_OK;
    
}

/*!
* \brief EN_AA register
*/
uint8_t nRF24_enable_auto_ack(uint8_t data_pipe, uint8_t enable) {
    uint8_t aa;
    uint8_t mask;
    if (data_pipe < 0 || data_pipe > 5) return HAL_ERROR;
    mask = 0x1 << data_pipe;
    if(nRF24_read_REGISTER(EN_AA, &aa, 1) != HAL_OK) return HAL_ERROR;
    if (enable) {
        aa |= mask;
    } else {
        aa &= (mask^0xFF);
    }
    return (nRF24_write_REGISTER(EN_AA, &aa,1));
}

/*!
* \param feature FEATURE_EN_DYN_ACK, FEATURE_EN_ACK_PAY, FEATURE_EN_DPL
* \param enable true:enable, false:disable
*/
uint8_t nRF24_enable_feature(uint8_t feature, uint8_t enable) {
    uint8_t buff;
    
    if(nRF24_read_REGISTER(FEATURE, &buff, 1) != HAL_OK) return HAL_ERROR;
    if (enable) {
        buff |= (0x01 << feature);
    } else {
        buff &= ((0x01 << feature) ^ 0xFF);
    }
    return (nRF24_write_REGISTER(FEATURE, &buff,1));
}

/*!
* \brief DYNPD register
 */
uint8_t nRF24_enable_data_pipe_dynamic_payload_length(uint8_t data_pipe, uint8_t enable) {
    if (data_pipe < 0 || data_pipe > 5) return HAL_ERROR;
    if (nRF24_enable_feature(FEATURE_EN_DPL, 1) != HAL_OK) return HAL_ERROR;
    if (nRF24_enable_auto_ack(data_pipe, 1) != HAL_OK) return HAL_ERROR;

    uint8_t dynpd;
    uint8_t mask;
    
    mask = 0x1 << data_pipe;
    if(nRF24_read_REGISTER(DYNPD, &dynpd, 1) != HAL_OK) return HAL_ERROR;
    if (enable) {
        dynpd |= mask;
    } else {
        dynpd &= (mask^0xff);
    }
    return (nRF24_write_REGISTER(DYNPD, &dynpd,1));
}

/*!
* \brief Received Power Detector (RPD)
*/
uint8_t nRF24_get_RPD(uint8_t *rpd_value) {
    return (nRF24_read_REGISTER(RPD, rpd_value, 1));
}

/*!
* \brief RX_PW_Px register, Number of bytes in RX payload in data pipe Px
*/
uint8_t nRF24_set_recv_payload_width(uint8_t data_pipe, uint8_t width) {
    if (width > 32 || width < 0) return HAL_ERROR;
    uint8_t rx_pw_px = RX_PW_P0+data_pipe;
    return (nRF24_write_REGISTER(rx_pw_px, &width, 1));
}
/*!
* \brief enable RX Address at data pipe x
* \param mask 0b00xxxxxx: ex. 0b00010111 enable p0~2 & p4
*/
uint8_t nRF24_enable_RXADDR(uint8_t mask) {
    return (nRF24_write_REGISTER(EN_RXADDR, &mask, 1));
}

uint8_t nRF24_set_data_rate(uint8_t rate) {
    uint8_t rf_setup;
    if(nRF24_read_REGISTER(RF_SETUP, &rf_setup, 1) != HAL_OK) return HAL_ERROR;
    rf_setup &= 0xD7;
    switch(rate) {
        case DATA_RATE_250K:
            rf_setup |= 0x20;
        break;
        case DATA_RATE_1M:
            rf_setup |= 0x00;
        break;
        case DATA_RATE_2M:
            rf_setup |= 0x08;
        break;
    }
    return (nRF24_write_REGISTER(RF_SETUP, &rf_setup,1));
    
}
/*!
* \brief set nRF24L01+ mode
* \param PRIM_RX 1:PRX, 0: PTX
* \return HAL status
*/
uint8_t nRF24_config_mode(uint8_t PRIM_RX) {
    
    uint8_t ret;
    uint8_t mode;
    
    ret = nRF24_read_REGISTER(CONFIG, &mode, 1);
    if (ret == HAL_OK) {
        nRF24_disable_CE();
        HAL_Delay(1);
        mode = (mode & 0xFE) | (PRIM_RX & 0x01);
        ret = nRF24_write_REGISTER(CONFIG, &mode,1);
        nRF24_enable_CE();
        HAL_Delay(1);
    }
    return ret;
}

/*!
* \param irq_type EVENT_MAX_RT, EVENT_TX_DS, EVENT_RX_DR
* \param enable
*/
uint8_t nRF24_enable_IRQ(uint8_t irq_type, uint8_t enable) {
    if (irq_type < EVENT_MAX_RT || irq_type > EVENT_RX_DR) return HAL_ERROR;

    uint8_t config;
    uint8_t mask=0x01 << irq_type;
    
    if (nRF24_read_REGISTER(CONFIG, &config, 1) != HAL_OK) return HAL_ERROR;
    if (enable) {
        config &= (mask ^ 0xFF);
    } else {
        config |= mask;
    }
    return nRF24_write_REGISTER(CONFIG, &config, 1);
}
/*!
* \brief clear MAX_RT interrupt
*/
uint8_t nRF24_clear_MAX_RT() {
    uint8_t status;
    if (nRF24_status(&status) != HAL_OK) return HAL_ERROR;
    if (status & 0x10) {
        status |= 0x10;
        return (nRF24_write_REGISTER(STATUS, &status, 1));
    }
    return HAL_ERROR;
}
/*!
* \brief clear TX_DS interrupt
*/
uint8_t nRF24_clear_TX_DS() {
    uint8_t status;
    if (nRF24_status(&status)!=HAL_OK) return HAL_ERROR;
    if (status & 0x20) {
        status |= 0x20;
        return (nRF24_write_REGISTER(STATUS, &status, 1));
    }
    return HAL_ERROR;
}

/*!
* \brief clear RX_DR interrupt
*/
uint8_t nRF24_clear_RX_DR() {
    uint8_t status;
    if (nRF24_status(&status) != HAL_OK) return HAL_ERROR;
    if (status & 0x40) {
        status |= 0x40;
        return (nRF24_write_REGISTER(STATUS, &status, 1));
    }
    return HAL_ERROR;
}

static void nRF24_standby_to_txrx_mode(){
    nRF24_enable_CE();
    //busy_wait_us(140);  // Standby modes --> TX/RX mode : max 130us.
                        // Delay from CE positive edge to CSN low : min 4us
    HAL_Delay(1);
}

/*!
* \brief set SETUP_RETR register bit 7:4 Auto Retransmit Delay
* \param delay time = 250us*(delay+1), value:0x00~0x0f
*/
uint8_t nRF24_set_auto_retransmit_delay(uint8_t delay) {
    uint8_t ard;
    if (delay > 0x0f) delay = 0x0f;
    if (nRF24_read_REGISTER(SETUP_RETR, &ard,1) == HAL_OK) {
        ard = (ard&0x0f) | delay << 4;
        return (nRF24_write_REGISTER(SETUP_RETR, &ard, 1));
    }
    return HAL_ERROR;
}

uint8_t nRF24_fifo_tx_full(uint8_t *full) {
    uint8_t fifo_status;
    uint8_t ret;
    ret = nRF24_read_REGISTER(FIFO_STATUS, &fifo_status, 1);
    if (ret == HAL_OK) {
        fifo_status &= 0x20;
        *full = fifo_status >> 5; 
    }
    return ret;
}

uint8_t nRF24_fifo_tx_empty(uint8_t *empty) {
    uint8_t fifo_status;
    uint8_t ret;
    ret = nRF24_read_REGISTER(FIFO_STATUS, &fifo_status, 1);
    if (ret == HAL_OK) {
        fifo_status &= 0x10;
        *empty = fifo_status >> 4; 
    }
    return ret;
}

uint8_t nRF24_fifo_rx_full(uint8_t *full) {
    uint8_t fifo_status;
    uint8_t ret;
    ret = nRF24_read_REGISTER(FIFO_STATUS, &fifo_status, 1);
    if (ret == HAL_OK) {
        fifo_status &= 0x02;
        *full = fifo_status  >> 1; 
    }
    return ret;
}

uint8_t nRF24_fifo_rx_empty(uint8_t *empty) {
    uint8_t fifo_status;
    uint8_t ret;
    ret = nRF24_read_REGISTER(FIFO_STATUS, &fifo_status, 1);
    if (ret == HAL_OK) {
        fifo_status &= 0x01;
        *empty = fifo_status;
    }
    return ret;
}

static void nRF24_reset_REGISTER() {
    uint8_t value;
    value=0x3f;
    nRF24_write_REGISTER(EN_AA, &value, 1);
    value=0x0a;
        nRF24_write_REGISTER(CONFIG, &value, 1);
    value=0x03;
    nRF24_write_REGISTER(EN_RXADDR, &value, 1);
    value=0x03;
    nRF24_write_REGISTER(SETUP_AW, &value, 1);
    value=0x03;
    nRF24_write_REGISTER(SETUP_RETR, &value, 1);
    value=0x02;
    nRF24_write_REGISTER(RF_CH, &value, 1);
    value=0x0f;
    nRF24_write_REGISTER(RF_SETUP, &value, 1);
    nRF24_flush_rx();  //reset STATUS
    nRF24_flush_tx();
    value=0x0e;
    nRF24_write_REGISTER(STATUS, &value, 1);
    value=0x00;
    nRF24_write_REGISTER(RX_PW_P0, &value, 1);
    nRF24_write_REGISTER(RX_PW_P1, &value, 1);
    nRF24_write_REGISTER(RX_PW_P2, &value, 1);
    nRF24_write_REGISTER(RX_PW_P3, &value, 1);
    nRF24_write_REGISTER(RX_PW_P4, &value, 1);
    nRF24_write_REGISTER(RX_PW_P5, &value, 1);
    nRF24_write_REGISTER(DYNPD, &value, 1);
    nRF24_write_REGISTER(FEATURE, &value, 1);
}

/*!
 * \brief HAL_GPIO_EXTI_Callback used by nRF24L01 driver. User must define nRF24_irq_callback
 *        to get interrupt event
 * \param event_type EVENT_RX_DR: Receiver Data Ready,
 * 					 EVENT_TX_DS: Transmitter Data Sent,
 *                   EVENT_MAX_RT:Transmitter retransmit
 *                   EVENT_GPIO_IRQ: gpio interrupt handler except nRF24L01 IRQ GPIO_pin
 *  \param data_src  nRF24L01 datapipe or GPIO_pin
 *  \param data		 nRF24L01 received data
 *  \param width	 nRF24L01 received data length
 */
__weak void nRF24_irq_callback(uint8_t event_type, uint16_t data_src, uint8_t* data, uint8_t width) {

      switch(event_type) {
        case EVENT_RX_DR:

        	break;
        case EVENT_TX_DS:

        	break;
        case EVENT_MAX_RT:

        break;
        case EVENT_GPIO_IRQ:

        	break;
    }
}
  • nRF24L01.h
 #ifndef __nRF24L01_H__
#define __nRF24L01_H__

// Command
#define R_REGISTER          0b00000000
#define W_REGISTER          0b00100000
#define R_RX_PAYLOAD        0b01100001
#define W_TX_PAYLOAD        0b10100000
#define FLUSH_TX            0b11100001
#define FLUSH_RX            0b11100010
#define REUSE_TX_PL         0b11100011
#define ACTIVATE            0b01010000      //nRF24L01 Product Specification
#define R_RX_PL_WID         0b01100000
#define W_ACK_PAYLOAD       0b10101000      // 0b10100PPP
#define W_TX_PAYLOAD_NOACK  0b10110000
#define NOP                 0b11111111

//Registers
#define CONFIG              0x00
#define EN_AA               0x01
#define EN_RXADDR           0x02
#define SETUP_AW            0x03
#define SETUP_RETR          0x04
#define RF_CH               0x05
#define RF_SETUP            0x06
#define STATUS              0x07
#define OBSERVE_TX          0x08
#define RPD                 0x09
#define RX_ADDR_P0          0x0A
#define RX_ADDR_P1          0x0B
#define RX_ADDR_P2          0x0C
#define RX_ADDR_P3          0x0D
#define RX_ADDR_P4          0x0E
#define RX_ADDR_P5          0x0F
#define TX_ADDR             0x10
#define RX_PW_P0            0x11
#define RX_PW_P1            0x12
#define RX_PW_P2            0x13
#define RX_PW_P3            0x14
#define RX_PW_P4            0x15
#define RX_PW_P5            0x16
#define FIFO_STATUS         0x17
#define DYNPD               0x1C
#define FEATURE             0x1D

#define TRANSMITTER         0
#define RECEIVER            1

typedef enum {
    DATA_RATE_250K=0,
    DATA_RATE_1M,
    DATA_RATE_2M
} nRF24_DATA_RATE;

typedef enum {
    PA_0dBm=0,
    PA_m_6dBm,
    PA_m_12dBm,
    PA_m_18dBm,
} nRF24_PA;

typedef enum {
    AW_3=1,
    AW_4,
    AW_5,
} nRF24_ADDRESS_WIDTH;

typedef enum {
    FEATURE_EN_DYN_ACK=0,  // W_TX_PAYLOAD_NOACK
    FEATURE_EN_ACK_PAY,
    FEATURE_EN_DPL,
}nRF24_FEATURE_MASK;

typedef enum {
    EVENT_MAX_RT=4,
    EVENT_TX_DS,
    EVENT_RX_DR,
	EVENT_GPIO_IRQ,
} nRF24_IRQ_EVENT;

//typedef void (*nRF24_irq_callback_t)(uint8_t evant_type, uint16_t data_src, uint8_t* data, uint8_t width);

void nRF24_spi_init(SPI_HandleTypeDef* spi,GPIO_TypeDef* csn_port, uint32_t csn_pin, GPIO_TypeDef* ce_port, uint32_t ce_pin,  GPIO_TypeDef* irq_port, uint32_t irq_pin);

uint8_t nRF24_status(uint8_t *status);
uint8_t  nRF24_read_REGISTER(uint8_t REGISTER, uint8_t *value, uint8_t len);
uint8_t  nRF24_write_REGISTER(uint8_t REGISTER, uint8_t *value, uint8_t len);
uint8_t nRF24_config_mode(uint8_t PRIM_RX);

uint8_t nRF24_set_data_rate(uint8_t rate);
uint8_t nRF24_set_RF_channel(uint8_t channel);
uint8_t nRF24_set_power_amplifier(uint8_t pa);
uint8_t nRF24_set_address_width(uint8_t aw);
uint8_t nRF24_set_auto_retransmit_delay(uint8_t delay);
uint8_t nRF24_set_recv_payload_width(uint8_t data_pipe, uint8_t width);
uint8_t nRF24_set_RX_addr(uint8_t data_pipe, uint8_t *addr, uint8_t len);
uint8_t nRF24_set_TX_addr(uint8_t *addr, uint8_t len);

uint8_t nRF24_get_RPD(uint8_t *rpd_value);
uint8_t nRF24_get_RX_payload_width(uint8_t *width);
uint8_t nRF24_get_address_width(uint8_t *aw);
uint8_t nRF24_get_auto_retransmit_delay(uint8_t *delay);
uint8_t nRF24_get_RX_addr(uint8_t data_pipe, uint8_t *addr, uint8_t len);
uint8_t nRF24_get_TX_addr(uint8_t *addr, uint8_t len);

uint8_t nRF24_enable_auto_ack(uint8_t data_pipe, uint8_t enable);
uint8_t nRF24_enable_data_pipe_dynamic_payload_length(uint8_t data_pipe, uint8_t enable);
uint8_t nRF24_enable_feature(uint8_t feature, uint8_t enable);
uint8_t nRF24_enable_RXADDR(uint8_t mask);

uint8_t nRF24_read_payload(uint8_t *payload, uint8_t len);
uint8_t nRF24_write_payload(uint8_t *payload, uint8_t len);
uint8_t nRF24_write_payload_no_ack(uint8_t *payload, uint8_t len);

uint8_t nRF24_flush_rx();
uint8_t nRF24_flush_tx();
uint8_t nRF24_reuse_tx_pl();
uint8_t nRF24_write_ack_payload(uint8_t data_pipe, uint8_t *payload, uint8_t len);

uint8_t nRF24_clear_MAX_RT();
uint8_t nRF24_clear_TX_DS();
uint8_t nRF24_clear_RX_DR();

uint8_t nRF24_fifo_tx_full(uint8_t *full);
uint8_t nRF24_fifo_rx_full(uint8_t *full);
uint8_t nRF24_fifo_tx_empty(uint8_t *empty);
uint8_t nRF24_fifo_rx_empty(uint8_t *empty);

void nRF24_irq_callback(uint8_t event_type, uint16_t data_src, uint8_t* data, uint8_t width);

#endif